diff --git a/dotnet/SK-dotnet.sln b/dotnet/SK-dotnet.sln index bde158c3c1ac..1d7a9caf756d 100644 --- a/dotnet/SK-dotnet.sln +++ b/dotnet/SK-dotnet.sln @@ -150,6 +150,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Planning.StepwisePlanner", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationInsightsExample", "samples\ApplicationInsightsExample\ApplicationInsightsExample.csproj", "{C754950A-E16C-4F96-9CC7-9328E361B5AF}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlayFabExamples", "samples\PlayFabExamples\PlayFabExamples.csproj", "{948F37AA-A437-4AFF-93C9-03C47A886B5D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -361,6 +363,12 @@ Global {C754950A-E16C-4F96-9CC7-9328E361B5AF}.Publish|Any CPU.ActiveCfg = Release|Any CPU {C754950A-E16C-4F96-9CC7-9328E361B5AF}.Release|Any CPU.ActiveCfg = Release|Any CPU {C754950A-E16C-4F96-9CC7-9328E361B5AF}.Release|Any CPU.Build.0 = Release|Any CPU + {948F37AA-A437-4AFF-93C9-03C47A886B5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {948F37AA-A437-4AFF-93C9-03C47A886B5D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {948F37AA-A437-4AFF-93C9-03C47A886B5D}.Publish|Any CPU.ActiveCfg = Debug|Any CPU + {948F37AA-A437-4AFF-93C9-03C47A886B5D}.Publish|Any CPU.Build.0 = Debug|Any CPU + {948F37AA-A437-4AFF-93C9-03C47A886B5D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {948F37AA-A437-4AFF-93C9-03C47A886B5D}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -413,6 +421,7 @@ Global {677F1381-7830-4115-9C1A-58B282629DC6} = {0247C2C9-86C3-45BA-8873-28B0948EDC0C} {4762BCAF-E1C5-4714-B88D-E50FA333C50E} = {078F96B4-09E1-4E0E-B214-F71A4F4BF633} {C754950A-E16C-4F96-9CC7-9328E361B5AF} = {FA3720F1-C99A-49B2-9577-A940257098BF} + {948F37AA-A437-4AFF-93C9-03C47A886B5D} = {FA3720F1-C99A-49B2-9577-A940257098BF} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {FBDC56A3-86AD-4323-AA0F-201E59123B83} diff --git a/dotnet/samples/KernelSyntaxExamples/KernelSyntaxExamples.csproj b/dotnet/samples/KernelSyntaxExamples/KernelSyntaxExamples.csproj index 244cadfa2ea4..ef49cf4d94ae 100644 --- a/dotnet/samples/KernelSyntaxExamples/KernelSyntaxExamples.csproj +++ b/dotnet/samples/KernelSyntaxExamples/KernelSyntaxExamples.csproj @@ -4,7 +4,8 @@ - net6.0 + net7.0 + 11 LatestMajor Exe false @@ -16,6 +17,7 @@ + @@ -23,6 +25,7 @@ + @@ -40,6 +43,7 @@ + diff --git a/dotnet/samples/KernelSyntaxExamples/Skills/PlayFabApiSkill/openapi.json b/dotnet/samples/KernelSyntaxExamples/Skills/PlayFabApiSkill/openapi.json new file mode 100644 index 000000000000..089eb8c915cb --- /dev/null +++ b/dotnet/samples/KernelSyntaxExamples/Skills/PlayFabApiSkill/openapi.json @@ -0,0 +1,4161 @@ +{ + "swagger": "2.0", + "info": { + "version": "230730", + "title": "PlayFab Admin API", + "description": "APIs for managing title configurations, uploaded Game Server code executables, and user data", + "termsOfService": "https://playfab.com/terms/", + "contact": { + "url": "https://community.playfab.com/index.html" + }, + "license": { + "name": "Apache 2.0", + "url": "https://github.com/PlayFab/API_Specs/blob/master/LICENSE" + } + }, + "host": "titleId.playfabapi.com", + "schemes": [ + "https" + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "paths": { + "/Admin/CreateSegment": { + "post": { + "operationId": "CreateSegment", + "tags": [ + "Segments" + ], + "description": "Creates a new player segment by defining the conditions on player properties. Also, create actions to target the player segments for a title.", + "externalDocs": { + "url": "https://docs.microsoft.com/rest/api/playfab/admin/segments/createsegment" + }, + "parameters": [ + { + "$ref": "#/parameters/CreateSegmentRequest" + } + ], + "security": [ + { + "SecretKey": [ + ] + } + ], + "responses": { + "200": { + "$ref": "#/responses/CreateSegmentResponse" + }, + "400": { + "$ref": "#/responses/ApiErrorWrapper" + } + }, + "x-requestDetails": "Send all the segment details part of CreateSegmentRequest", + "x-ms-docs-errors": [ + "InvalidSegment", + "NameNotAvailable", + "SegmentManagementInvalidInput", + "SegmentManagementInvalidSegmentId", + "SegmentManagementInvalidSegmentName", + "SegmentManagementNoExpressionTree", + "SegmentManagementSegmentCountOverLimit", + "SegmentManagementTriggerActionCountOverLimit", + "UnknownError" + ] + } + }, + "/Admin/DeleteSegment": { + "post": { + "operationId": "DeleteSegment", + "tags": [ + "Segments" + ], + "description": "Deletes an existing player segment and its associated action(s) for a title.", + "externalDocs": { + "url": "https://docs.microsoft.com/rest/api/playfab/admin/segments/deletesegment" + }, + "parameters": [ + { + "$ref": "#/parameters/DeleteSegmentRequest" + } + ], + "security": [ + { + "SecretKey": [ + ] + } + ], + "responses": { + "200": { + "$ref": "#/responses/DeleteSegmentsResponse" + }, + "400": { + "$ref": "#/responses/ApiErrorWrapper" + } + }, + "x-requestDetails": "Send segment id planning to delete part of DeleteSegmentRequest object", + "x-ms-docs-errors": [ + "SegmentManagementInvalidSegmentId", + "UnknownError" + ] + } + }, + "/Admin/GetDataReport": { + "post": { + "operationId": "GetDataReport", + "tags": [ + "Player Data Management" + ], + "description": "Retrieves a download URL for the requested report", + "externalDocs": { + "url": "https://docs.microsoft.com/rest/api/playfab/admin/player-data-management/getdatareport" + }, + "parameters": [ + { + "$ref": "#/parameters/GetDataReportRequest" + } + ], + "security": [ + { + "SecretKey": [ + ] + } + ], + "responses": { + "200": { + "$ref": "#/responses/GetDataReportResult" + }, + "400": { + "$ref": "#/responses/ApiErrorWrapper" + } + }, + "x-requestDetails": "Gets the download URL for the requested report data (in CSV form). The reports available through this API call are those available in the Game Manager, in the Analytics->Reports tab.", + "x-ms-docs-errors": [ + "InvalidReportDate", + "ReportNotAvailable" + ] + } + }, + "/Admin/GetPlayerStatisticDefinitions": { + "post": { + "operationId": "GetPlayerStatisticDefinitions", + "tags": [ + "Player Data Management" + ], + "description": "Retrieves the configuration information for all player statistics defined in the title, regardless of whether they have a reset interval.", + "externalDocs": { + "url": "https://docs.microsoft.com/rest/api/playfab/admin/player-data-management/getplayerstatisticdefinitions" + }, + "parameters": [ + { + "$ref": "#/parameters/GetPlayerStatisticDefinitionsRequest" + } + ], + "security": [ + { + "SecretKey": [ + ] + } + ], + "responses": { + "200": { + "$ref": "#/responses/GetPlayerStatisticDefinitionsResult" + }, + "400": { + "$ref": "#/responses/ApiErrorWrapper" + } + }, + "x-responseDetails": "Statistics are numeric values, with each statistic in the title also generating a leaderboard. The ResetInterval defines the period of time at which the leaderboard for the statistic will automatically reset. Upon reset, the statistic updates to a new version with no values (effectively removing all players from the leaderboard). The previous version's statistic values are also archived for retrieval, if needed (see GetPlayerStatisticVersions). Statistics not created via a call to CreatePlayerStatisticDefinition by default have a VersionChangeInterval of Never, meaning they do not reset on a schedule, but they can be set to do so via a call to UpdatePlayerStatisticDefinition. Once a statistic has been reset (sometimes referred to as versioned or incremented), the previous version can still be written to for up a short, pre-defined period (currently 10 seconds), to prevent issues with levels completing around the time of the reset. Also, once reset, the historical statistics for players in the title may be retrieved using the URL specified in the version information (GetPlayerStatisticVersions). The AggregationMethod defines what action is taken when a new statistic value is submitted - always update with the new value (Last), use the highest of the old and new values (Max), use the smallest (Min), or add them together (Sum).", + "x-seeAlso": [ + "Admin/CreatePlayerStatisticDefinition", + "Client/GetPlayerStatistics", + "Server/GetPlayerStatistics", + "Admin/UpdatePlayerStatisticDefinition", + "Client/UpdatePlayerStatistics", + "Server/UpdatePlayerStatistics" + ] + } + }, + "/Admin/GetPlayerStatisticVersions": { + "post": { + "operationId": "GetPlayerStatisticVersions", + "tags": [ + "Player Data Management" + ], + "description": "Retrieves the information on the available versions of the specified statistic.", + "externalDocs": { + "url": "https://docs.microsoft.com/rest/api/playfab/admin/player-data-management/getplayerstatisticversions" + }, + "parameters": [ + { + "$ref": "#/parameters/GetPlayerStatisticVersionsRequest" + } + ], + "security": [ + { + "SecretKey": [ + ] + } + ], + "responses": { + "200": { + "$ref": "#/responses/GetPlayerStatisticVersionsResult" + }, + "400": { + "$ref": "#/responses/ApiErrorWrapper" + } + }, + "x-responseDetails": "Statistics are numeric values, with each statistic in the title also generating a leaderboard. The information returned in the results defines the state of a specific version of a statistic, including when it was or will become the currently active version, when it will (or did) become a previous version, and its archival state if it is no longer the active version. For a statistic which has been reset, once the archival status is Complete, the full set of statistics for all players in the leaderboard for that version may be retrieved via the ArchiveDownloadUrl. Statistics which have not been reset (incremented/versioned) will only have a single version which is not scheduled to reset.", + "x-seeAlso": [ + "Admin/CreatePlayerStatisticDefinition", + "Admin/GetPlayerStatisticDefinitions", + "Client/GetPlayerStatistics", + "Server/GetPlayerStatistics", + "Admin/UpdatePlayerStatisticDefinition", + "Client/UpdatePlayerStatistics", + "Server/UpdatePlayerStatistics" + ], + "x-ms-docs-errors": [ + "StatisticNotFound" + ] + } + }, + "/Admin/GetSegments": { + "post": { + "operationId": "GetSegments", + "tags": [ + "Segments" + ], + "description": "Get detail information of a segment and its associated definition(s) and action(s) for a title.", + "externalDocs": { + "url": "https://docs.microsoft.com/rest/api/playfab/admin/segments/getsegments" + }, + "parameters": [ + { + "$ref": "#/parameters/GetSegmentsRequest" + } + ], + "security": [ + { + "SecretKey": [ + ] + } + ], + "responses": { + "200": { + "$ref": "#/responses/GetSegmentsResponse" + }, + "400": { + "$ref": "#/responses/ApiErrorWrapper" + } + }, + "x-requestDetails": "Send segment filter details part of GetSegmentsRequest object", + "x-ms-docs-errors": [ + "GetSegmentsRateLimitExceeded", + "UnknownError" + ] + } + } + }, + "definitions": { + "ApiErrorWrapper": { + "description": "The basic wrapper around every failed API response", + "type": "object", + "properties": { + "code": { + "description": "Numerical HTTP code", + "type": "integer" + }, + "status": { + "description": "String HTTP code", + "type": "string" + }, + "error": { + "description": "Playfab error code", + "type": "string" + }, + "errorCode": { + "description": "Numerical PlayFab error code", + "type": "integer" + }, + "errorMessage": { + "description": "Description for the PlayFab errorCode", + "type": "string" + }, + "errorDetails": { + "description": "Detailed description of individual issues with the request object", + "type": "object" + } + }, + "required": [ + "code", + "errorCode" + ] + }, + "AdCampaignAttribution": { + "type": "object", + "properties": { + "AttributedAt": { + "description": "UTC time stamp of attribution", + "type": "string" + }, + "CampaignId": { + "description": "Attribution campaign identifier", + "type": "string" + }, + "Platform": { + "description": "Attribution network name", + "type": "string" + } + }, + "required": [ + "AttributedAt" + ] + }, + "AdCampaignAttributionModel": { + "type": "object", + "properties": { + "AttributedAt": { + "description": "UTC time stamp of attribution", + "type": "string" + }, + "CampaignId": { + "description": "Attribution campaign identifier", + "type": "string" + }, + "Platform": { + "description": "Attribution network name", + "type": "string" + } + }, + "required": [ + "AttributedAt" + ] + }, + "AdCampaignSegmentFilter": { + "type": "object", + "properties": { + "CampaignId": { + "description": "Campaign id.", + "type": "string" + }, + "CampaignSource": { + "description": "Campaign source.", + "type": "string" + }, + "Comparison": { + "$ref": "#/definitions/SegmentFilterComparison", + "description": "Campaign comparison.", + "type": "string", + "x-isenum": true + } + } + }, + "AllPlayersSegmentFilter": { + "type": "object", + "properties": { + } + }, + "BanInfo": { + "description": "Contains information for a ban.", + "type": "object", + "properties": { + "Active": { + "description": "The active state of this ban. Expired bans may still have this value set to true but they will have no effect.", + "type": "boolean" + }, + "BanId": { + "description": "The unique Ban Id associated with this ban.", + "type": "string" + }, + "Created": { + "description": "The time when this ban was applied.", + "type": "string" + }, + "Expires": { + "description": "The time when this ban expires. Permanent bans do not have expiration date.", + "type": "string" + }, + "IPAddress": { + "description": "The IP address on which the ban was applied. May affect multiple players.", + "type": "string" + }, + "PlayFabId": { + "description": "Unique PlayFab assigned ID of the user on whom the operation will be performed.", + "type": "string" + }, + "Reason": { + "description": "The reason why this ban was applied.", + "type": "string" + } + }, + "required": [ + "Active" + ] + }, + "BanPlayerSegmentAction": { + "type": "object", + "properties": { + "BanHours": { + "description": "Ban hours duration.", + "type": "number", + "x-actualtype": "uint32" + }, + "ReasonForBan": { + "description": "Reason for ban.", + "type": "string" + } + } + }, + "BanRequest": { + "description": "Represents a single ban request.", + "type": "object", + "properties": { + "DurationInHours": { + "description": "The duration in hours for the ban. Leave this blank for a permanent ban.", + "type": "number", + "x-actualtype": "uint32" + }, + "IPAddress": { + "description": "IP address to be banned. May affect multiple players.", + "type": "string" + }, + "PlayFabId": { + "description": "Unique PlayFab assigned ID of the user on whom the operation will be performed.", + "type": "string" + }, + "Reason": { + "description": "The reason for this ban. Maximum 140 characters.", + "type": "string" + } + }, + "required": [ + "PlayFabId" + ] + }, + "BlankResult": { + "type": "object", + "properties": { + } + }, + "ChurnPredictionSegmentFilter": { + "type": "object", + "properties": { + "Comparison": { + "$ref": "#/definitions/SegmentFilterComparison", + "description": "Comparison", + "type": "string", + "x-isenum": true + }, + "RiskLevel": { + "$ref": "#/definitions/ChurnRiskLevel", + "description": "RiskLevel", + "type": "string", + "x-isenum": true + } + } + }, + "ChurnRiskLevel": { + "type": "string", + "enum": [ + "NoData", + "LowRisk", + "MediumRisk", + "HighRisk" + ] + }, + "Conditionals": { + "type": "string", + "enum": [ + "Any", + "True", + "False" + ] + }, + "ContactEmailInfo": { + "type": "object", + "properties": { + "EmailAddress": { + "description": "The email address", + "type": "string" + }, + "Name": { + "description": "The name of the email info data", + "type": "string" + }, + "VerificationStatus": { + "$ref": "#/definitions/EmailVerificationStatus", + "description": "The verification status of the email", + "type": "string", + "x-isenum": true + } + } + }, + "ContactEmailInfoModel": { + "type": "object", + "properties": { + "EmailAddress": { + "description": "The email address", + "type": "string" + }, + "Name": { + "description": "The name of the email info data", + "type": "string" + }, + "VerificationStatus": { + "$ref": "#/definitions/EmailVerificationStatus", + "description": "The verification status of the email", + "type": "string", + "x-isenum": true + } + } + }, + "ContinentCode": { + "type": "string", + "enum": [ + "AF", + "AN", + "AS", + "EU", + "NA", + "OC", + "SA" + ] + }, + "CountryCode": { + "type": "string", + "enum": [ + "AF", + "AX", + "AL", + "DZ", + "AS", + "AD", + "AO", + "AI", + "AQ", + "AG", + "AR", + "AM", + "AW", + "AU", + "AT", + "AZ", + "BS", + "BH", + "BD", + "BB", + "BY", + "BE", + "BZ", + "BJ", + "BM", + "BT", + "BO", + "BQ", + "BA", + "BW", + "BV", + "BR", + "IO", + "BN", + "BG", + "BF", + "BI", + "KH", + "CM", + "CA", + "CV", + "KY", + "CF", + "TD", + "CL", + "CN", + "CX", + "CC", + "CO", + "KM", + "CG", + "CD", + "CK", + "CR", + "CI", + "HR", + "CU", + "CW", + "CY", + "CZ", + "DK", + "DJ", + "DM", + "DO", + "EC", + "EG", + "SV", + "GQ", + "ER", + "EE", + "ET", + "FK", + "FO", + "FJ", + "FI", + "FR", + "GF", + "PF", + "TF", + "GA", + "GM", + "GE", + "DE", + "GH", + "GI", + "GR", + "GL", + "GD", + "GP", + "GU", + "GT", + "GG", + "GN", + "GW", + "GY", + "HT", + "HM", + "VA", + "HN", + "HK", + "HU", + "IS", + "IN", + "ID", + "IR", + "IQ", + "IE", + "IM", + "IL", + "IT", + "JM", + "JP", + "JE", + "JO", + "KZ", + "KE", + "KI", + "KP", + "KR", + "KW", + "KG", + "LA", + "LV", + "LB", + "LS", + "LR", + "LY", + "LI", + "LT", + "LU", + "MO", + "MK", + "MG", + "MW", + "MY", + "MV", + "ML", + "MT", + "MH", + "MQ", + "MR", + "MU", + "YT", + "MX", + "FM", + "MD", + "MC", + "MN", + "ME", + "MS", + "MA", + "MZ", + "MM", + "NA", + "NR", + "NP", + "NL", + "NC", + "NZ", + "NI", + "NE", + "NG", + "NU", + "NF", + "MP", + "NO", + "OM", + "PK", + "PW", + "PS", + "PA", + "PG", + "PY", + "PE", + "PH", + "PN", + "PL", + "PT", + "PR", + "QA", + "RE", + "RO", + "RU", + "RW", + "BL", + "SH", + "KN", + "LC", + "MF", + "PM", + "VC", + "WS", + "SM", + "ST", + "SA", + "SN", + "RS", + "SC", + "SL", + "SG", + "SX", + "SK", + "SI", + "SB", + "SO", + "ZA", + "GS", + "SS", + "ES", + "LK", + "SD", + "SR", + "SJ", + "SZ", + "SE", + "CH", + "SY", + "TW", + "TJ", + "TZ", + "TH", + "TL", + "TG", + "TK", + "TO", + "TT", + "TN", + "TR", + "TM", + "TC", + "TV", + "UG", + "UA", + "AE", + "GB", + "US", + "UM", + "UY", + "UZ", + "VU", + "VE", + "VN", + "VG", + "VI", + "WF", + "EH", + "YE", + "ZM", + "ZW" + ] + }, + "CreateSegmentRequest": { + "description": "Send all the segment details part of CreateSegmentRequest", + "type": "object", + "properties": { + "SegmentModel": { + "$ref": "#/definitions/SegmentModel", + "description": "Segment model with all of the segment properties data.", + "type": "object", + "x-isclass": true + } + }, + "required": [ + "SegmentModel" + ], + "example": { + "SegmentModel": { + "Name": "My user segment", + "SegmentOrDefinitions": [ + { + "SegmentAndDefinitions": [ + { + "AllPlayersFilter": { + } + } + ] + } + ], + "EnteredSegmentActions": [ + { + "BanPlayerAction": { + "BanHours": 2, + "ReasonForBan": "Not played for a year" + } + } + ], + "LeftSegmentActions": [ + { + "ExecuteAzureFunctionAction": { + "AzureFunction": "AzureFunction1", + "FunctionParameter": "{\"InputParam\": \"1\"}" + } + } + ] + } + } + }, + "CreateSegmentResponse": { + "type": "object", + "properties": { + "ErrorMessage": { + "description": "Error message.", + "type": "string" + }, + "SegmentId": { + "description": "Segment id.", + "type": "string" + } + } + }, + "Currency": { + "type": "string", + "enum": [ + "AED", + "AFN", + "ALL", + "AMD", + "ANG", + "AOA", + "ARS", + "AUD", + "AWG", + "AZN", + "BAM", + "BBD", + "BDT", + "BGN", + "BHD", + "BIF", + "BMD", + "BND", + "BOB", + "BRL", + "BSD", + "BTN", + "BWP", + "BYR", + "BZD", + "CAD", + "CDF", + "CHF", + "CLP", + "CNY", + "COP", + "CRC", + "CUC", + "CUP", + "CVE", + "CZK", + "DJF", + "DKK", + "DOP", + "DZD", + "EGP", + "ERN", + "ETB", + "EUR", + "FJD", + "FKP", + "GBP", + "GEL", + "GGP", + "GHS", + "GIP", + "GMD", + "GNF", + "GTQ", + "GYD", + "HKD", + "HNL", + "HRK", + "HTG", + "HUF", + "IDR", + "ILS", + "IMP", + "INR", + "IQD", + "IRR", + "ISK", + "JEP", + "JMD", + "JOD", + "JPY", + "KES", + "KGS", + "KHR", + "KMF", + "KPW", + "KRW", + "KWD", + "KYD", + "KZT", + "LAK", + "LBP", + "LKR", + "LRD", + "LSL", + "LYD", + "MAD", + "MDL", + "MGA", + "MKD", + "MMK", + "MNT", + "MOP", + "MRO", + "MUR", + "MVR", + "MWK", + "MXN", + "MYR", + "MZN", + "NAD", + "NGN", + "NIO", + "NOK", + "NPR", + "NZD", + "OMR", + "PAB", + "PEN", + "PGK", + "PHP", + "PKR", + "PLN", + "PYG", + "QAR", + "RON", + "RSD", + "RUB", + "RWF", + "SAR", + "SBD", + "SCR", + "SDG", + "SEK", + "SGD", + "SHP", + "SLL", + "SOS", + "SPL", + "SRD", + "STD", + "SVC", + "SYP", + "SZL", + "THB", + "TJS", + "TMT", + "TND", + "TOP", + "TRY", + "TTD", + "TVD", + "TWD", + "TZS", + "UAH", + "UGX", + "USD", + "UYU", + "UZS", + "VEF", + "VND", + "VUV", + "WST", + "XAF", + "XCD", + "XDR", + "XOF", + "XPF", + "YER", + "ZAR", + "ZMW", + "ZWD" + ] + }, + "DeletePlayerRequest": { + "description": "Deletes all data associated with the player, including statistics, custom data, inventory, purchases, virtual currency balances, characters and shared group memberships. Removes the player from all leaderboards and player search indexes. Does not delete PlayStream event history associated with the player. Does not delete the publisher user account that created the player in the title nor associated data such as username, password, email address, account linkages, or friends list. Note, this API queues the player for deletion and returns immediately. It may take several minutes or more before all player data is fully deleted. Until the player data is fully deleted, attempts to recreate the player with the same user account in the same title will fail with the 'AccountDeleted' error.", + "type": "object", + "properties": { + "PlayFabId": { + "description": "Unique PlayFab assigned ID of the user on whom the operation will be performed.", + "type": "string" + } + }, + "required": [ + "PlayFabId" + ], + "example": { + "PlayFabId": "A68C908B66C23C5" + } + }, + "DeletePlayerResult": { + "type": "object", + "properties": { + } + }, + "DeletePlayerSegmentAction": { + "type": "object", + "properties": { + } + }, + "DeletePlayerSharedSecretRequest": { + "description": "Player Shared Secret Keys are used for the call to Client/GetTitlePublicKey, which exchanges the shared secret for an RSA CSP blob to be used to encrypt the payload of account creation requests when that API requires a signature header.", + "type": "object", + "properties": { + "SecretKey": { + "description": "The shared secret key to delete", + "type": "string" + } + }, + "example": { + "SecretKey": "keysaresecret" + } + }, + "DeletePlayerSharedSecretResult": { + "type": "object", + "properties": { + } + }, + "DeletePlayerStatisticSegmentAction": { + "type": "object", + "properties": { + "StatisticName": { + "description": "Statistic name.", + "type": "string" + } + } + }, + "DeleteSegmentRequest": { + "description": "Send segment id planning to delete part of DeleteSegmentRequest object", + "type": "object", + "properties": { + "SegmentId": { + "description": "Segment id.", + "type": "string" + } + }, + "required": [ + "SegmentId" + ], + "example": { + "SegmentId": "97EF0E9302CBE996" + } + }, + "DeleteSegmentsResponse": { + "type": "object", + "properties": { + "ErrorMessage": { + "description": "Error message.", + "type": "string" + } + } + }, + "EffectType": { + "type": "string", + "enum": [ + "Allow", + "Deny" + ] + }, + "EmailNotificationSegmentAction": { + "type": "object", + "properties": { + "EmailTemplateId": { + "description": "Email template id.", + "type": "string" + }, + "EmailTemplateName": { + "description": "Email template name.", + "type": "string" + } + } + }, + "EmptyResponse": { + "type": "object", + "properties": { + } + }, + "EmailVerificationStatus": { + "type": "string", + "enum": [ + "Unverified", + "Pending", + "Confirmed" + ] + }, + "ExecuteAzureFunctionSegmentAction": { + "type": "object", + "properties": { + "AzureFunction": { + "description": "Azure function.", + "type": "string" + }, + "FunctionParameter": { + "description": "Azure function parameter.", + "type": "object" + }, + "GenerateFunctionExecutedEvents": { + "description": "Generate play stream event.", + "type": "boolean" + } + }, + "required": [ + "GenerateFunctionExecutedEvents" + ] + }, + "ExecuteCloudScriptSegmentAction": { + "type": "object", + "properties": { + "CloudScriptFunction": { + "description": "Cloud script function.", + "type": "string" + }, + "CloudScriptPublishResultsToPlayStream": { + "description": "Generate play stream event.", + "type": "boolean" + }, + "FunctionParameter": { + "description": "Cloud script function parameter.", + "type": "object" + }, + "FunctionParameterJson": { + "description": "Cloud script function parameter json text.", + "type": "string" + } + }, + "required": [ + "CloudScriptPublishResultsToPlayStream" + ] + }, + "FirstLoginDateSegmentFilter": { + "type": "object", + "properties": { + "Comparison": { + "$ref": "#/definitions/SegmentFilterComparison", + "description": "First player login date comparison.", + "type": "string", + "x-isenum": true + }, + "LogInDate": { + "description": "First player login date.", + "type": "string" + } + }, + "required": [ + "LogInDate" + ] + }, + "FirstLoginTimespanSegmentFilter": { + "type": "object", + "properties": { + "Comparison": { + "$ref": "#/definitions/SegmentFilterComparison", + "description": "First player login duration comparison.", + "type": "string", + "x-isenum": true + }, + "DurationInMinutes": { + "description": "First player login duration.", + "type": "number", + "x-actualtype": "double" + } + }, + "required": [ + "DurationInMinutes" + ] + }, + "GenericErrorCodes": { + "type": "string", + "enum": [ + "Success", + "UnkownError", + "InvalidParams", + "AccountNotFound", + "AccountBanned", + "InvalidUsernameOrPassword", + "InvalidTitleId", + "InvalidEmailAddress", + "EmailAddressNotAvailable", + "InvalidUsername", + "InvalidPassword", + "UsernameNotAvailable", + "InvalidSteamTicket", + "AccountAlreadyLinked", + "LinkedAccountAlreadyClaimed", + "InvalidFacebookToken", + "AccountNotLinked", + "FailedByPaymentProvider", + "CouponCodeNotFound", + "InvalidContainerItem", + "ContainerNotOwned", + "KeyNotOwned", + "InvalidItemIdInTable", + "InvalidReceipt", + "ReceiptAlreadyUsed", + "ReceiptCancelled", + "GameNotFound", + "GameModeNotFound", + "InvalidGoogleToken", + "UserIsNotPartOfDeveloper", + "InvalidTitleForDeveloper", + "TitleNameConflicts", + "UserisNotValid", + "ValueAlreadyExists", + "BuildNotFound", + "PlayerNotInGame", + "InvalidTicket", + "InvalidDeveloper", + "InvalidOrderInfo", + "RegistrationIncomplete", + "InvalidPlatform", + "UnknownError", + "SteamApplicationNotOwned", + "WrongSteamAccount", + "TitleNotActivated", + "RegistrationSessionNotFound", + "NoSuchMod", + "FileNotFound", + "DuplicateEmail", + "ItemNotFound", + "ItemNotOwned", + "ItemNotRecycleable", + "ItemNotAffordable", + "InvalidVirtualCurrency", + "WrongVirtualCurrency", + "WrongPrice", + "NonPositiveValue", + "InvalidRegion", + "RegionAtCapacity", + "ServerFailedToStart", + "NameNotAvailable", + "InsufficientFunds", + "InvalidDeviceID", + "InvalidPushNotificationToken", + "NoRemainingUses", + "InvalidPaymentProvider", + "PurchaseInitializationFailure", + "DuplicateUsername", + "InvalidBuyerInfo", + "NoGameModeParamsSet", + "BodyTooLarge", + "ReservedWordInBody", + "InvalidTypeInBody", + "InvalidRequest", + "ReservedEventName", + "InvalidUserStatistics", + "NotAuthenticated", + "StreamAlreadyExists", + "ErrorCreatingStream", + "StreamNotFound", + "InvalidAccount", + "PurchaseDoesNotExist", + "InvalidPurchaseTransactionStatus", + "APINotEnabledForGameClientAccess", + "NoPushNotificationARNForTitle", + "BuildAlreadyExists", + "BuildPackageDoesNotExist", + "CustomAnalyticsEventsNotEnabledForTitle", + "InvalidSharedGroupId", + "NotAuthorized", + "MissingTitleGoogleProperties", + "InvalidItemProperties", + "InvalidPSNAuthCode", + "InvalidItemId", + "PushNotEnabledForAccount", + "PushServiceError", + "ReceiptDoesNotContainInAppItems", + "ReceiptContainsMultipleInAppItems", + "InvalidBundleID", + "JavascriptException", + "InvalidSessionTicket", + "UnableToConnectToDatabase", + "InternalServerError", + "InvalidReportDate", + "ReportNotAvailable", + "DatabaseThroughputExceeded", + "InvalidGameTicket", + "ExpiredGameTicket", + "GameTicketDoesNotMatchLobby", + "LinkedDeviceAlreadyClaimed", + "DeviceAlreadyLinked", + "DeviceNotLinked", + "PartialFailure", + "PublisherNotSet", + "ServiceUnavailable", + "VersionNotFound", + "RevisionNotFound", + "InvalidPublisherId", + "DownstreamServiceUnavailable", + "APINotIncludedInTitleUsageTier", + "DAULimitExceeded", + "APIRequestLimitExceeded", + "InvalidAPIEndpoint", + "BuildNotAvailable", + "ConcurrentEditError", + "ContentNotFound", + "CharacterNotFound", + "CloudScriptNotFound", + "ContentQuotaExceeded", + "InvalidCharacterStatistics", + "PhotonNotEnabledForTitle", + "PhotonApplicationNotFound", + "PhotonApplicationNotAssociatedWithTitle", + "InvalidEmailOrPassword", + "FacebookAPIError", + "InvalidContentType", + "KeyLengthExceeded", + "DataLengthExceeded", + "TooManyKeys", + "FreeTierCannotHaveVirtualCurrency", + "MissingAmazonSharedKey", + "AmazonValidationError", + "InvalidPSNIssuerId", + "PSNInaccessible", + "ExpiredAuthToken", + "FailedToGetEntitlements", + "FailedToConsumeEntitlement", + "TradeAcceptingUserNotAllowed", + "TradeInventoryItemIsAssignedToCharacter", + "TradeInventoryItemIsBundle", + "TradeStatusNotValidForCancelling", + "TradeStatusNotValidForAccepting", + "TradeDoesNotExist", + "TradeCancelled", + "TradeAlreadyFilled", + "TradeWaitForStatusTimeout", + "TradeInventoryItemExpired", + "TradeMissingOfferedAndAcceptedItems", + "TradeAcceptedItemIsBundle", + "TradeAcceptedItemIsStackable", + "TradeInventoryItemInvalidStatus", + "TradeAcceptedCatalogItemInvalid", + "TradeAllowedUsersInvalid", + "TradeInventoryItemDoesNotExist", + "TradeInventoryItemIsConsumed", + "TradeInventoryItemIsStackable", + "TradeAcceptedItemsMismatch", + "InvalidKongregateToken", + "FeatureNotConfiguredForTitle", + "NoMatchingCatalogItemForReceipt", + "InvalidCurrencyCode", + "NoRealMoneyPriceForCatalogItem", + "TradeInventoryItemIsNotTradable", + "TradeAcceptedCatalogItemIsNotTradable", + "UsersAlreadyFriends", + "LinkedIdentifierAlreadyClaimed", + "CustomIdNotLinked", + "TotalDataSizeExceeded", + "DeleteKeyConflict", + "InvalidXboxLiveToken", + "ExpiredXboxLiveToken", + "ResettableStatisticVersionRequired", + "NotAuthorizedByTitle", + "NoPartnerEnabled", + "InvalidPartnerResponse", + "APINotEnabledForGameServerAccess", + "StatisticNotFound", + "StatisticNameConflict", + "StatisticVersionClosedForWrites", + "StatisticVersionInvalid", + "APIClientRequestRateLimitExceeded", + "InvalidJSONContent", + "InvalidDropTable", + "StatisticVersionAlreadyIncrementedForScheduledInterval", + "StatisticCountLimitExceeded", + "StatisticVersionIncrementRateExceeded", + "ContainerKeyInvalid", + "CloudScriptExecutionTimeLimitExceeded", + "NoWritePermissionsForEvent", + "CloudScriptFunctionArgumentSizeExceeded", + "CloudScriptAPIRequestCountExceeded", + "CloudScriptAPIRequestError", + "CloudScriptHTTPRequestError", + "InsufficientGuildRole", + "GuildNotFound", + "OverLimit", + "EventNotFound", + "InvalidEventField", + "InvalidEventName", + "CatalogNotConfigured", + "OperationNotSupportedForPlatform", + "SegmentNotFound", + "StoreNotFound", + "InvalidStatisticName", + "TitleNotQualifiedForLimit", + "InvalidServiceLimitLevel", + "ServiceLimitLevelInTransition", + "CouponAlreadyRedeemed", + "GameServerBuildSizeLimitExceeded", + "GameServerBuildCountLimitExceeded", + "VirtualCurrencyCountLimitExceeded", + "VirtualCurrencyCodeExists", + "TitleNewsItemCountLimitExceeded", + "InvalidTwitchToken", + "TwitchResponseError", + "ProfaneDisplayName", + "UserAlreadyAdded", + "InvalidVirtualCurrencyCode", + "VirtualCurrencyCannotBeDeleted", + "IdentifierAlreadyClaimed", + "IdentifierNotLinked", + "InvalidContinuationToken", + "ExpiredContinuationToken", + "InvalidSegment", + "InvalidSessionId", + "SessionLogNotFound", + "InvalidSearchTerm", + "TwoFactorAuthenticationTokenRequired", + "GameServerHostCountLimitExceeded", + "PlayerTagCountLimitExceeded", + "RequestAlreadyRunning", + "ActionGroupNotFound", + "MaximumSegmentBulkActionJobsRunning", + "NoActionsOnPlayersInSegmentJob", + "DuplicateStatisticName", + "ScheduledTaskNameConflict", + "ScheduledTaskCreateConflict", + "InvalidScheduledTaskName", + "InvalidTaskSchedule", + "SteamNotEnabledForTitle", + "LimitNotAnUpgradeOption", + "NoSecretKeyEnabledForCloudScript", + "TaskNotFound", + "TaskInstanceNotFound", + "InvalidIdentityProviderId", + "MisconfiguredIdentityProvider", + "InvalidScheduledTaskType", + "BillingInformationRequired", + "LimitedEditionItemUnavailable", + "InvalidAdPlacementAndReward", + "AllAdPlacementViewsAlreadyConsumed", + "GoogleOAuthNotConfiguredForTitle", + "GoogleOAuthError", + "UserNotFriend", + "InvalidSignature", + "InvalidPublicKey", + "GoogleOAuthNoIdTokenIncludedInResponse", + "StatisticUpdateInProgress", + "LeaderboardVersionNotAvailable", + "StatisticAlreadyHasPrizeTable", + "PrizeTableHasOverlappingRanks", + "PrizeTableHasMissingRanks", + "PrizeTableRankStartsAtZero", + "InvalidStatistic", + "ExpressionParseFailure", + "ExpressionInvokeFailure", + "ExpressionTooLong", + "DataUpdateRateExceeded", + "RestrictedEmailDomain", + "EncryptionKeyDisabled", + "EncryptionKeyMissing", + "EncryptionKeyBroken", + "NoSharedSecretKeyConfigured", + "SecretKeyNotFound", + "PlayerSecretAlreadyConfigured", + "APIRequestsDisabledForTitle", + "InvalidSharedSecretKey", + "PrizeTableHasNoRanks", + "ProfileDoesNotExist", + "ContentS3OriginBucketNotConfigured", + "InvalidEnvironmentForReceipt", + "EncryptedRequestNotAllowed", + "SignedRequestNotAllowed", + "RequestViewConstraintParamsNotAllowed", + "BadPartnerConfiguration", + "XboxBPCertificateFailure", + "XboxXASSExchangeFailure", + "InvalidEntityId", + "StatisticValueAggregationOverflow", + "EmailMessageFromAddressIsMissing", + "EmailMessageToAddressIsMissing", + "SmtpServerAuthenticationError", + "SmtpServerLimitExceeded", + "SmtpServerInsufficientStorage", + "SmtpServerCommunicationError", + "SmtpServerGeneralFailure", + "EmailClientTimeout", + "EmailClientCanceledTask", + "EmailTemplateMissing", + "InvalidHostForTitleId", + "EmailConfirmationTokenDoesNotExist", + "EmailConfirmationTokenExpired", + "AccountDeleted", + "PlayerSecretNotConfigured", + "InvalidSignatureTime", + "NoContactEmailAddressFound", + "InvalidAuthToken", + "AuthTokenDoesNotExist", + "AuthTokenExpired", + "AuthTokenAlreadyUsedToResetPassword", + "MembershipNameTooLong", + "MembershipNotFound", + "GoogleServiceAccountInvalid", + "GoogleServiceAccountParseFailure", + "EntityTokenMissing", + "EntityTokenInvalid", + "EntityTokenExpired", + "EntityTokenRevoked", + "InvalidProductForSubscription", + "XboxInaccessible", + "SubscriptionAlreadyTaken", + "SmtpAddonNotEnabled", + "APIConcurrentRequestLimitExceeded", + "XboxRejectedXSTSExchangeRequest", + "VariableNotDefined", + "TemplateVersionNotDefined", + "FileTooLarge", + "TitleDeleted", + "TitleContainsUserAccounts", + "TitleDeletionPlayerCleanupFailure", + "EntityFileOperationPending", + "NoEntityFileOperationPending", + "EntityProfileVersionMismatch", + "TemplateVersionTooOld", + "MembershipDefinitionInUse", + "PaymentPageNotConfigured", + "FailedLoginAttemptRateLimitExceeded", + "EntityBlockedByGroup", + "RoleDoesNotExist", + "EntityIsAlreadyMember", + "DuplicateRoleId", + "GroupInvitationNotFound", + "GroupApplicationNotFound", + "OutstandingInvitationAcceptedInstead", + "OutstandingApplicationAcceptedInstead", + "RoleIsGroupDefaultMember", + "RoleIsGroupAdmin", + "RoleNameNotAvailable", + "GroupNameNotAvailable", + "EmailReportAlreadySent", + "EmailReportRecipientBlacklisted", + "EventNamespaceNotAllowed", + "EventEntityNotAllowed", + "InvalidEntityType", + "NullTokenResultFromAad", + "InvalidTokenResultFromAad", + "NoValidCertificateForAad", + "InvalidCertificateForAad", + "DuplicateDropTableId", + "MultiplayerServerError", + "MultiplayerServerTooManyRequests", + "MultiplayerServerNoContent", + "MultiplayerServerBadRequest", + "MultiplayerServerUnauthorized", + "MultiplayerServerForbidden", + "MultiplayerServerNotFound", + "MultiplayerServerConflict", + "MultiplayerServerInternalServerError", + "MultiplayerServerUnavailable", + "ExplicitContentDetected", + "PIIContentDetected", + "InvalidScheduledTaskParameter", + "PerEntityEventRateLimitExceeded", + "TitleDefaultLanguageNotSet", + "EmailTemplateMissingDefaultVersion", + "FacebookInstantGamesIdNotLinked", + "InvalidFacebookInstantGamesSignature", + "FacebookInstantGamesAuthNotConfiguredForTitle", + "EntityProfileConstraintValidationFailed", + "TelemetryIngestionKeyPending", + "TelemetryIngestionKeyNotFound", + "StatisticChildNameInvalid", + "DataIntegrityError", + "VirtualCurrencyCannotBeSetToOlderVersion", + "VirtualCurrencyMustBeWithinIntegerRange", + "EmailTemplateInvalidSyntax", + "EmailTemplateMissingCallback", + "PushNotificationTemplateInvalidPayload", + "InvalidLocalizedPushNotificationLanguage", + "MissingLocalizedPushNotificationMessage", + "PushNotificationTemplateMissingPlatformPayload", + "PushNotificationTemplatePayloadContainsInvalidJson", + "PushNotificationTemplateContainsInvalidIosPayload", + "PushNotificationTemplateContainsInvalidAndroidPayload", + "PushNotificationTemplateIosPayloadMissingNotificationBody", + "PushNotificationTemplateAndroidPayloadMissingNotificationBody", + "PushNotificationTemplateNotFound", + "PushNotificationTemplateMissingDefaultVersion", + "PushNotificationTemplateInvalidSyntax", + "PushNotificationTemplateNoCustomPayloadForV1", + "NoLeaderboardForStatistic", + "TitleNewsMissingDefaultLanguage", + "TitleNewsNotFound", + "TitleNewsDuplicateLanguage", + "TitleNewsMissingTitleOrBody", + "TitleNewsInvalidLanguage", + "EmailRecipientBlacklisted", + "InvalidGameCenterAuthRequest", + "GameCenterAuthenticationFailed", + "CannotEnablePartiesForTitle", + "PartyError", + "PartyRequests", + "PartyNoContent", + "PartyBadRequest", + "PartyUnauthorized", + "PartyForbidden", + "PartyNotFound", + "PartyConflict", + "PartyInternalServerError", + "PartyUnavailable", + "PartyTooManyRequests", + "PushNotificationTemplateMissingName", + "CannotEnableMultiplayerServersForTitle", + "WriteAttemptedDuringExport", + "MultiplayerServerTitleQuotaCoresExceeded", + "AutomationRuleNotFound", + "EntityAPIKeyLimitExceeded", + "EntityAPIKeyNotFound", + "EntityAPIKeyOrSecretInvalid", + "EconomyServiceUnavailable", + "EconomyServiceInternalError", + "QueryRateLimitExceeded", + "EntityAPIKeyCreationDisabledForEntity", + "ForbiddenByEntityPolicy", + "UpdateInventoryRateLimitExceeded", + "StudioCreationRateLimited", + "StudioCreationInProgress", + "DuplicateStudioName", + "StudioNotFound", + "StudioDeleted", + "StudioDeactivated", + "StudioActivated", + "TitleCreationRateLimited", + "TitleCreationInProgress", + "DuplicateTitleName", + "TitleActivationRateLimited", + "TitleActivationInProgress", + "TitleDeactivated", + "TitleActivated", + "CloudScriptAzureFunctionsExecutionTimeLimitExceeded", + "CloudScriptAzureFunctionsArgumentSizeExceeded", + "CloudScriptAzureFunctionsReturnSizeExceeded", + "CloudScriptAzureFunctionsHTTPRequestError", + "VirtualCurrencyBetaGetError", + "VirtualCurrencyBetaCreateError", + "VirtualCurrencyBetaInitialDepositSaveError", + "VirtualCurrencyBetaSaveError", + "VirtualCurrencyBetaDeleteError", + "VirtualCurrencyBetaRestoreError", + "VirtualCurrencyBetaSaveConflict", + "VirtualCurrencyBetaUpdateError", + "InsightsManagementDatabaseNotFound", + "InsightsManagementOperationNotFound", + "InsightsManagementErrorPendingOperationExists", + "InsightsManagementSetPerformanceLevelInvalidParameter", + "InsightsManagementSetStorageRetentionInvalidParameter", + "InsightsManagementGetStorageUsageInvalidParameter", + "InsightsManagementGetOperationStatusInvalidParameter", + "DuplicatePurchaseTransactionId", + "EvaluationModePlayerCountExceeded", + "GetPlayersInSegmentRateLimitExceeded", + "CloudScriptFunctionNameSizeExceeded", + "PaidInsightsFeaturesNotEnabled", + "CloudScriptAzureFunctionsQueueRequestError", + "EvaluationModeTitleCountExceeded", + "InsightsManagementTitleNotInFlight", + "LimitNotFound", + "LimitNotAvailableViaAPI", + "InsightsManagementSetStorageRetentionBelowMinimum", + "InsightsManagementSetStorageRetentionAboveMaximum", + "AppleNotEnabledForTitle", + "InsightsManagementNewActiveEventExportLimitInvalid", + "InsightsManagementSetPerformanceRateLimited", + "PartyRequestsThrottledFromRateLimiter", + "XboxServiceTooManyRequests", + "NintendoSwitchNotEnabledForTitle", + "RequestMultiplayerServersThrottledFromRateLimiter", + "TitleDataOverrideNotFound", + "DuplicateKeys", + "WasNotCreatedWithCloudRoot", + "LegacyMultiplayerServersDeprecated", + "VirtualCurrencyCurrentlyUnavailable", + "SteamUserNotFound", + "ElasticSearchOperationFailed", + "NotImplemented", + "PublisherNotFound", + "PublisherDeleted", + "ApiDisabledForMigration", + "ResourceNameUpdateNotAllowed", + "ApiNotEnabledForTitle", + "DuplicateTitleNameForPublisher", + "AzureTitleCreationInProgress", + "TitleConstraintsPublisherDeletion", + "InvalidPlayerAccountPoolId", + "PlayerAccountPoolNotFound", + "PlayerAccountPoolDeleted", + "TitleCleanupInProgress", + "AzureResourceConcurrentOperationInProgress", + "TitlePublisherUpdateNotAllowed", + "AzureResourceManagerNotSupportedInStamp", + "ApiNotIncludedInAzurePlayFabFeatureSet", + "GoogleServiceAccountFailedAuth", + "GoogleAPIServiceUnavailable", + "GoogleAPIServiceUnknownError", + "NoValidIdentityForAad", + "PlayerIdentityLinkNotFound", + "PhotonApplicationIdAlreadyInUse", + "CloudScriptUnableToDeleteProductionRevision", + "CustomIdNotFound", + "AutomationInvalidInput", + "AutomationInvalidRuleName", + "AutomationRuleAlreadyExists", + "AutomationRuleLimitExceeded", + "InvalidGooglePlayGamesServerAuthCode", + "PlayStreamConnectionFailed", + "InvalidEventContents", + "InsightsV1Deprecated", + "AnalysisSubscriptionNotFound", + "AnalysisSubscriptionFailed", + "AnalysisSubscriptionFoundAlready", + "AnalysisSubscriptionManagementInvalidInput", + "InvalidGameCenterId", + "InvalidNintendoSwitchAccountId", + "EntityAPIKeysNotSupported", + "IpAddressBanned", + "EntityLineageBanned", + "NamespaceMismatch", + "InvalidServiceConfiguration", + "InvalidNamespaceMismatch", + "MatchmakingEntityInvalid", + "MatchmakingPlayerAttributesInvalid", + "MatchmakingQueueNotFound", + "MatchmakingMatchNotFound", + "MatchmakingTicketNotFound", + "MatchmakingAlreadyJoinedTicket", + "MatchmakingTicketAlreadyCompleted", + "MatchmakingQueueConfigInvalid", + "MatchmakingMemberProfileInvalid", + "NintendoSwitchDeviceIdNotLinked", + "MatchmakingNotEnabled", + "MatchmakingPlayerAttributesTooLarge", + "MatchmakingNumberOfPlayersInTicketTooLarge", + "MatchmakingAttributeInvalid", + "MatchmakingPlayerHasNotJoinedTicket", + "MatchmakingRateLimitExceeded", + "MatchmakingTicketMembershipLimitExceeded", + "MatchmakingUnauthorized", + "MatchmakingQueueLimitExceeded", + "MatchmakingRequestTypeMismatch", + "MatchmakingBadRequest", + "PubSubFeatureNotEnabledForTitle", + "PubSubTooManyRequests", + "PubSubConnectionNotFoundForEntity", + "PubSubConnectionHandleInvalid", + "PubSubSubscriptionLimitExceeded", + "TitleConfigNotFound", + "TitleConfigUpdateConflict", + "TitleConfigSerializationError", + "CatalogApiNotImplemented", + "CatalogEntityInvalid", + "CatalogTitleIdMissing", + "CatalogPlayerIdMissing", + "CatalogClientIdentityInvalid", + "CatalogOneOrMoreFilesInvalid", + "CatalogItemMetadataInvalid", + "CatalogItemIdInvalid", + "CatalogSearchParameterInvalid", + "CatalogFeatureDisabled", + "CatalogConfigInvalid", + "CatalogItemTypeInvalid", + "CatalogBadRequest", + "CatalogTooManyRequests", + "ExportInvalidStatusUpdate", + "ExportInvalidPrefix", + "ExportBlobContainerDoesNotExist", + "ExportNotFound", + "ExportCouldNotUpdate", + "ExportInvalidStorageType", + "ExportAmazonBucketDoesNotExist", + "ExportInvalidBlobStorage", + "ExportKustoException", + "ExportKustoConnectionFailed", + "ExportUnknownError", + "ExportCantEditPendingExport", + "ExportLimitExports", + "ExportLimitEvents", + "ExportInvalidPartitionStatusModification", + "ExportCouldNotCreate", + "ExportNoBackingDatabaseFound", + "ExportCouldNotDelete", + "ExportCannotDetermineEventQuery", + "ExportInvalidQuerySchemaModification", + "ExportQuerySchemaMissingRequiredColumns", + "ExportCannotParseQuery", + "ExportControlCommandsNotAllowed", + "ExportQueryMissingTableReference", + "ExportInsightsV1Deprecated", + "ExplorerBasicInvalidQueryName", + "ExplorerBasicInvalidQueryDescription", + "ExplorerBasicInvalidQueryConditions", + "ExplorerBasicInvalidQueryStartDate", + "ExplorerBasicInvalidQueryEndDate", + "ExplorerBasicInvalidQueryGroupBy", + "ExplorerBasicInvalidQueryAggregateType", + "ExplorerBasicInvalidQueryAggregateProperty", + "ExplorerBasicLoadQueriesError", + "ExplorerBasicLoadQueryError", + "ExplorerBasicCreateQueryError", + "ExplorerBasicDeleteQueryError", + "ExplorerBasicUpdateQueryError", + "ExplorerBasicSavedQueriesLimit", + "ExplorerBasicSavedQueryNotFound", + "TenantShardMapperShardNotFound", + "TitleNotEnabledForParty", + "PartyVersionNotFound", + "MultiplayerServerBuildReferencedByMatchmakingQueue", + "MultiplayerServerBuildReferencedByBuildAlias", + "MultiplayerServerBuildAliasReferencedByMatchmakingQueue", + "PartySerializationError", + "ExperimentationExperimentStopped", + "ExperimentationExperimentRunning", + "ExperimentationExperimentNotFound", + "ExperimentationExperimentNeverStarted", + "ExperimentationExperimentDeleted", + "ExperimentationClientTimeout", + "ExperimentationInvalidVariantConfiguration", + "ExperimentationInvalidVariableConfiguration", + "ExperimentInvalidId", + "ExperimentationNoScorecard", + "ExperimentationTreatmentAssignmentFailed", + "ExperimentationTreatmentAssignmentDisabled", + "ExperimentationInvalidDuration", + "ExperimentationMaxExperimentsReached", + "ExperimentationExperimentSchedulingInProgress", + "ExperimentationInvalidEndDate", + "ExperimentationInvalidStartDate", + "ExperimentationMaxDurationExceeded", + "ExperimentationExclusionGroupNotFound", + "ExperimentationExclusionGroupInsufficientCapacity", + "ExperimentationExclusionGroupCannotDelete", + "ExperimentationExclusionGroupInvalidTrafficAllocation", + "ExperimentationExclusionGroupInvalidName", + "MaxActionDepthExceeded", + "TitleNotOnUpdatedPricingPlan", + "SegmentManagementTitleNotInFlight", + "SegmentManagementNoExpressionTree", + "SegmentManagementTriggerActionCountOverLimit", + "SegmentManagementSegmentCountOverLimit", + "SegmentManagementInvalidSegmentId", + "SegmentManagementInvalidInput", + "SegmentManagementInvalidSegmentName", + "DeleteSegmentRateLimitExceeded", + "CreateSegmentRateLimitExceeded", + "UpdateSegmentRateLimitExceeded", + "GetSegmentsRateLimitExceeded", + "AsyncExportNotInFlight", + "AsyncExportNotFound", + "AsyncExportRateLimitExceeded", + "AnalyticsSegmentCountOverLimit", + "SnapshotNotFound", + "InventoryApiNotImplemented", + "LobbyDoesNotExist", + "LobbyRateLimitExceeded", + "LobbyPlayerAlreadyJoined", + "LobbyNotJoinable", + "LobbyMemberCannotRejoin", + "LobbyCurrentPlayersMoreThanMaxPlayers", + "LobbyPlayerNotPresent", + "LobbyBadRequest", + "LobbyPlayerMaxLobbyLimitExceeded", + "LobbyNewOwnerMustBeConnected", + "LobbyCurrentOwnerStillConnected", + "LobbyMemberIsNotOwner", + "EventSamplingInvalidRatio", + "EventSamplingInvalidEventNamespace", + "EventSamplingInvalidEventName", + "EventSamplingRatioNotFound", + "TelemetryKeyNotFound", + "TelemetryKeyInvalidName", + "TelemetryKeyAlreadyExists", + "TelemetryKeyInvalid", + "TelemetryKeyCountOverLimit", + "TelemetryKeyDeactivated", + "TelemetryKeyLongInsightsRetentionNotAllowed", + "EventSinkConnectionInvalid", + "EventSinkConnectionUnauthorized", + "EventSinkRegionInvalid", + "EventSinkLimitExceeded", + "EventSinkSasTokenInvalid", + "EventSinkNotFound", + "EventSinkNameInvalid", + "EventSinkSasTokenPermissionInvalid", + "EventSinkSecretInvalid", + "EventSinkTenantNotFound", + "EventSinkAadNotFound", + "EventSinkDatabaseNotFound", + "OperationCanceled", + "InvalidDisplayNameRandomSuffixLength", + "AllowNonUniquePlayerDisplayNamesDisableNotAllowed", + "PartitionedEventInvalid", + "PartitionedEventCountOverLimit", + "PlayerCustomPropertiesPropertyNameTooLong", + "PlayerCustomPropertiesPropertyNameIsInvalid", + "PlayerCustomPropertiesStringPropertyValueTooLong", + "PlayerCustomPropertiesValueIsInvalidType", + "PlayerCustomPropertiesVersionMismatch", + "PlayerCustomPropertiesPropertyCountTooHigh", + "PlayerCustomPropertiesDuplicatePropertyName", + "PlayerCustomPropertiesPropertyDoesNotExist", + "AddonAlreadyExists", + "AddonDoesntExist" + ] + }, + "GetContentUploadUrlResult": { + "type": "object", + "properties": { + "URL": { + "description": "URL for uploading content via HTTP PUT method. The URL requires the 'x-ms-blob-type' header to have the value 'BlockBlob'. The URL will expire in approximately one hour.", + "type": "string" + } + }, + "example": { + "URL": "https://..." + } + }, + "GetDataReportRequest": { + "description": "Gets the download URL for the requested report data (in CSV form). The reports available through this API call are those available in the Game Manager, in the Analytics->Reports tab.", + "type": "object", + "properties": { + "Day": { + "description": "Reporting year (UTC)", + "type": "number", + "x-actualtype": "int32" + }, + "Month": { + "description": "Reporting month (UTC)", + "type": "number", + "x-actualtype": "int32" + }, + "ReportName": { + "description": "Report name", + "type": "string" + }, + "Year": { + "description": "Reporting year (UTC)", + "type": "number", + "x-actualtype": "int32" + } + }, + "required": [ + "ReportName", + "Year", + "Month", + "Day" + ], + "example": { + "ReportName": "PurchaseDataReport", + "Year": 2014, + "Month": 12, + "Day": 5 + } + }, + "GetDataReportResult": { + "type": "object", + "properties": { + "DownloadUrl": { + "description": "The URL where the requested report can be downloaded. This can be any PlayFab generated reports. The full list of reports can be found at: https://docs.microsoft.com/en-us/gaming/playfab/features/analytics/reports/quickstart.", + "type": "string" + } + }, + "example": { + "DownloadUrl": "https://data-reports-dev.s3-us-west-1.amazonaws.com/2730/PurchaseDataReport/2014/12/5/data.json?AWSAccessKeyId=AKIAJB5VEITH4PHABPAA&Expires=1418151409&Signature=1cF0Xre4FETLW%2BIu5BCW0TL8ShQ%3D" + } + }, + "GetPlayerStatisticDefinitionsRequest": { + "type": "object", + "properties": { + }, + "example": { + } + }, + "GetPlayerStatisticDefinitionsResult": { + "description": "Statistics are numeric values, with each statistic in the title also generating a leaderboard. The ResetInterval defines the period of time at which the leaderboard for the statistic will automatically reset. Upon reset, the statistic updates to a new version with no values (effectively removing all players from the leaderboard). The previous version's statistic values are also archived for retrieval, if needed (see GetPlayerStatisticVersions). Statistics not created via a call to CreatePlayerStatisticDefinition by default have a VersionChangeInterval of Never, meaning they do not reset on a schedule, but they can be set to do so via a call to UpdatePlayerStatisticDefinition. Once a statistic has been reset (sometimes referred to as versioned or incremented), the previous version can still be written to for up a short, pre-defined period (currently 10 seconds), to prevent issues with levels completing around the time of the reset. Also, once reset, the historical statistics for players in the title may be retrieved using the URL specified in the version information (GetPlayerStatisticVersions). The AggregationMethod defines what action is taken when a new statistic value is submitted - always update with the new value (Last), use the highest of the old and new values (Max), use the smallest (Min), or add them together (Sum).", + "type": "object", + "properties": { + "Statistics": { + "description": "the player statistic definitions for the title", + "type": "array", + "items": { + "$ref": "#/definitions/PlayerStatisticDefinition" + }, + "x-isclass": true + } + }, + "example": { + "Statistics": [ + { + "StatisticName": "headshots", + "CurrentVersion": 2, + "VersionChangeInterval": "Day" + } + ] + } + }, + "GetPlayerStatisticVersionsRequest": { + "type": "object", + "properties": { + "CustomTags": { + "description": "The optional custom tags associated with the request (e.g. build number, external trace identifiers, etc.).", + "type": "object" + }, + "StatisticName": { + "description": "unique name of the statistic", + "type": "string" + } + }, + "example": { + "StatisticName": "headshots" + } + }, + "GetPlayerStatisticVersionsResult": { + "description": "Statistics are numeric values, with each statistic in the title also generating a leaderboard. The information returned in the results defines the state of a specific version of a statistic, including when it was or will become the currently active version, when it will (or did) become a previous version, and its archival state if it is no longer the active version. For a statistic which has been reset, once the archival status is Complete, the full set of statistics for all players in the leaderboard for that version may be retrieved via the ArchiveDownloadUrl. Statistics which have not been reset (incremented/versioned) will only have a single version which is not scheduled to reset.", + "type": "object", + "properties": { + "StatisticVersions": { + "description": "version change history of the statistic", + "type": "array", + "items": { + "$ref": "#/definitions/PlayerStatisticVersion" + }, + "x-isclass": true + } + }, + "example": { + "StatisticVersions": [ + { + "StatisticName": "headshots", + "Version": 2, + "ActivationTime": "2016-12-05T10:02:01", + "Status": "Archived", + "ArchiveDownloadUrl": "https://Someurl" + } + ] + } + }, + "GetSegmentResult": { + "type": "object", + "properties": { + "ABTestParent": { + "description": "Identifier of the segments AB Test, if it is attached to one.", + "type": "string" + }, + "Id": { + "description": "Unique identifier for this segment.", + "type": "string" + }, + "Name": { + "description": "Segment name.", + "type": "string" + } + }, + "required": [ + "Id" + ], + "example": { + "Id": "1337AA00", + "Name": "Fast Players" + } + }, + "GetSegmentsRequest": { + "description": "Given input segment ids, return list of segments.", + "type": "object", + "properties": { + "SegmentIds": { + "description": "Segment ids to filter title segments.", + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "SegmentIds" + ], + "example": { + "SegmentIds": [ + "B05FC8CB558A6570", + "97EF0E9302CBE996" + ] + } + }, + "GetSegmentsResponse": { + "type": "object", + "properties": { + "ErrorMessage": { + "description": "Error message.", + "type": "string" + }, + "Segments": { + "description": "List of title segments.", + "type": "array", + "items": { + "$ref": "#/definitions/SegmentModel" + }, + "x-isclass": true + } + } + }, + "GrantedItemInstance": { + "description": "Result of granting an item to a user. Note, to retrieve additional information for an item such as Tags, Description that are the same across all instances of the item, a call to GetCatalogItems is required. The ItemID of can be matched to a catalog entry, which contains the additional information. Also note that Custom Data is only set when the User's specific instance has updated the CustomData via a call to UpdateUserInventoryItemCustomData. Other fields such as UnitPrice and UnitCurrency are only set when the item was granted via a purchase.", + "type": "object", + "properties": { + "Annotation": { + "description": "Game specific comment associated with this instance when it was added to the user inventory.", + "type": "string" + }, + "BundleContents": { + "description": "Array of unique items that were awarded when this catalog item was purchased.", + "type": "array", + "items": { + "type": "string" + } + }, + "BundleParent": { + "description": "Unique identifier for the parent inventory item, as defined in the catalog, for object which were added from a bundle or container.", + "type": "string" + }, + "CatalogVersion": { + "description": "Catalog version for the inventory item, when this instance was created.", + "type": "string" + }, + "CharacterId": { + "description": "Unique PlayFab assigned ID for a specific character owned by a user", + "type": "string" + }, + "CustomData": { + "description": "A set of custom key-value pairs on the instance of the inventory item, which is not to be confused with the catalog item's custom data.", + "type": "object" + }, + "DisplayName": { + "description": "CatalogItem.DisplayName at the time this item was purchased.", + "type": "string" + }, + "Expiration": { + "description": "Timestamp for when this instance will expire.", + "type": "string" + }, + "ItemClass": { + "description": "Class name for the inventory item, as defined in the catalog.", + "type": "string" + }, + "ItemId": { + "description": "Unique identifier for the inventory item, as defined in the catalog.", + "type": "string" + }, + "ItemInstanceId": { + "description": "Unique item identifier for this specific instance of the item.", + "type": "string" + }, + "PlayFabId": { + "description": "Unique PlayFab assigned ID of the user on whom the operation will be performed.", + "type": "string" + }, + "PurchaseDate": { + "description": "Timestamp for when this instance was purchased.", + "type": "string" + }, + "RemainingUses": { + "description": "Total number of remaining uses, if this is a consumable item.", + "type": "number", + "x-actualtype": "int32" + }, + "Result": { + "description": "Result of this operation.", + "type": "boolean" + }, + "UnitCurrency": { + "description": "Currency type for the cost of the catalog item. Not available when granting items.", + "type": "string" + }, + "UnitPrice": { + "description": "Cost of the catalog item in the given currency. Not available when granting items.", + "type": "number", + "x-actualtype": "uint32" + }, + "UsesIncrementedBy": { + "description": "The number of uses that were added or removed to this item in this call.", + "type": "number", + "x-actualtype": "int32" + } + }, + "required": [ + "Result", + "UnitPrice" + ], + "example": { + "PlayFabId": "20394883", + "Result": true, + "ItemId": "shield_level_5", + "ItemInstanceId": "133713371337", + "ItemClass": "shields", + "PurchaseDate": "2017-03-07T00:00:00", + "Expiration": "2018-10-01T00:00:00", + "RemainingUses": 10, + "Annotation": "Entered Level 2", + "CatalogVersion": "5", + "BundleParent": "crate block 1" + }, + "x-sortKey": "ItemInstanceId" + }, + "GrantItemSegmentAction": { + "type": "object", + "properties": { + "CatelogId": { + "description": "Item catalog id.", + "type": "string" + }, + "ItemId": { + "description": "Item id.", + "type": "string" + }, + "Quantity": { + "description": "Item quantity.", + "type": "number", + "x-actualtype": "uint32" + } + }, + "required": [ + "Quantity" + ] + }, + "GrantVirtualCurrencySegmentAction": { + "type": "object", + "properties": { + "Amount": { + "description": "Virtual currency amount.", + "type": "number", + "x-actualtype": "int32" + }, + "CurrencyCode": { + "description": "Virtual currency code.", + "type": "string" + } + }, + "required": [ + "Amount" + ] + }, + "IncrementLimitedEditionItemAvailabilityRequest": { + "description": "This operation will increment the global counter for the number of these items available. This number cannot be decremented, except by actual grants.", + "type": "object", + "properties": { + "Amount": { + "description": "Amount to increase availability by.", + "type": "number", + "x-actualtype": "int32" + }, + "CatalogVersion": { + "description": "Which catalog is being updated. If null, uses the default catalog.", + "type": "string" + }, + "CustomTags": { + "description": "The optional custom tags associated with the request (e.g. build number, external trace identifiers, etc.).", + "type": "object" + }, + "ItemId": { + "description": "The item which needs more availability.", + "type": "string" + } + }, + "required": [ + "ItemId", + "Amount" + ], + "example": { + "CatalogVersion": "Rares", + "ItemId": "FreeHoodie", + "Amount": 50 + } + }, + "IncrementLimitedEditionItemAvailabilityResult": { + "type": "object", + "properties": { + }, + "example": { + } + }, + "IncrementPlayerStatisticSegmentAction": { + "type": "object", + "properties": { + "IncrementValue": { + "description": "Increment value.", + "type": "number", + "x-actualtype": "int32" + }, + "StatisticName": { + "description": "Statistic name.", + "type": "string" + } + }, + "required": [ + "IncrementValue" + ] + }, + "IncrementPlayerStatisticVersionRequest": { + "description": "Statistics are numeric values, with each statistic in the title also generating a leaderboard. When this call is made on a given statistic, this forces a reset of that statistic. Upon reset, the statistic updates to a new version with no values (effectively removing all players from the leaderboard). The previous version's statistic values are also archived for retrieval, if needed (see GetPlayerStatisticVersions). Statistics not created via a call to CreatePlayerStatisticDefinition by default have a VersionChangeInterval of Never, meaning they do not reset on a schedule, but they can be set to do so via a call to UpdatePlayerStatisticDefinition. Once a statistic has been reset (sometimes referred to as versioned or incremented), the now-previous version can still be written to for up a short, pre-defined period (currently 10 seconds), to prevent issues with levels completing around the time of the reset. Also, once reset, the historical statistics for players in the title may be retrieved using the URL specified in the version information (GetPlayerStatisticVersions).", + "type": "object", + "properties": { + "CustomTags": { + "description": "The optional custom tags associated with the request (e.g. build number, external trace identifiers, etc.).", + "type": "object" + }, + "StatisticName": { + "description": "unique name of the statistic", + "type": "string" + } + }, + "example": { + "StatisticName": "headshots" + } + }, + "IncrementPlayerStatisticVersionResult": { + "type": "object", + "properties": { + "StatisticVersion": { + "$ref": "#/definitions/PlayerStatisticVersion", + "description": "version change history of the statistic", + "type": "object", + "x-isclass": true + } + }, + "example": { + "StatisticVersion": { + "StatisticName": "headshots", + "Version": 2, + "ActivationTime": "2016-12-05T10:02:01", + "Status": "Archived", + "ArchiveDownloadUrl": "https://Someurl" + } + } + }, + "InsightsScalingTaskParameter": { + "type": "object", + "properties": { + "Level": { + "description": "Insights Performance Level to scale to.", + "type": "number", + "x-actualtype": "int32" + } + }, + "required": [ + "Level" + ] + }, + "LastLoginDateSegmentFilter": { + "type": "object", + "properties": { + "Comparison": { + "$ref": "#/definitions/SegmentFilterComparison", + "description": "Last player login date comparison.", + "type": "string", + "x-isenum": true + }, + "LogInDate": { + "description": "Last player login date.", + "type": "string" + } + }, + "required": [ + "LogInDate" + ] + }, + "LastLoginTimespanSegmentFilter": { + "type": "object", + "properties": { + "Comparison": { + "$ref": "#/definitions/SegmentFilterComparison", + "description": "Last player login duration comparison.", + "type": "string", + "x-isenum": true + }, + "DurationInMinutes": { + "description": "Last player login duration.", + "type": "number", + "x-actualtype": "double" + } + }, + "required": [ + "DurationInMinutes" + ] + }, + "LinkedPlatformAccountModel": { + "type": "object", + "properties": { + "Email": { + "description": "Linked account email of the user on the platform, if available", + "type": "string" + }, + "Platform": { + "$ref": "#/definitions/LoginIdentityProvider", + "description": "Authentication platform", + "type": "string", + "x-isenum": true + }, + "PlatformUserId": { + "description": "Unique account identifier of the user on the platform", + "type": "string" + }, + "Username": { + "description": "Linked account username of the user on the platform, if available", + "type": "string" + } + } + }, + "LinkedUserAccountHasEmailSegmentFilter": { + "type": "object", + "properties": { + "Comparison": { + "$ref": "#/definitions/SegmentFilterComparison", + "description": "Login provider comparison.", + "type": "string", + "x-isenum": true + }, + "LoginProvider": { + "$ref": "#/definitions/SegmentLoginIdentityProvider", + "description": "Login provider.", + "type": "string", + "x-isenum": true + } + } + }, + "LinkedUserAccountSegmentFilter": { + "type": "object", + "properties": { + "LoginProvider": { + "$ref": "#/definitions/SegmentLoginIdentityProvider", + "description": "Login provider.", + "type": "string", + "x-isenum": true + } + } + }, + "LocationModel": { + "type": "object", + "properties": { + "City": { + "description": "City name.", + "type": "string" + }, + "ContinentCode": { + "$ref": "#/definitions/ContinentCode", + "description": "The two-character continent code for this location", + "type": "string", + "x-isenum": true + }, + "CountryCode": { + "$ref": "#/definitions/CountryCode", + "description": "The two-character ISO 3166-1 country code for the country associated with the location", + "type": "string", + "x-isenum": true + }, + "Latitude": { + "description": "Latitude coordinate of the geographic location.", + "type": "number", + "x-actualtype": "double" + }, + "Longitude": { + "description": "Longitude coordinate of the geographic location.", + "type": "number", + "x-actualtype": "double" + } + } + }, + "LocationSegmentFilter": { + "type": "object", + "properties": { + "CountryCode": { + "$ref": "#/definitions/SegmentCountryCode", + "description": "Segment country code.", + "type": "string", + "x-isenum": true + } + } + }, + "LoginIdentityProvider": { + "type": "string", + "enum": [ + "Unknown", + "PlayFab", + "Custom", + "GameCenter", + "GooglePlay", + "Steam", + "XBoxLive", + "PSN", + "Kongregate", + "Facebook", + "IOSDevice", + "AndroidDevice", + "Twitch", + "WindowsHello", + "GameServer", + "CustomServer", + "NintendoSwitch", + "FacebookInstantGames", + "OpenIdConnect", + "Apple", + "NintendoSwitchAccount", + "GooglePlayGames" + ] + }, + "LogStatement": { + "type": "object", + "properties": { + "Data": { + "description": "Optional object accompanying the message as contextual information", + "type": "object" + }, + "Level": { + "description": "'Debug', 'Info', or 'Error'", + "type": "string" + }, + "Message": { + "type": "string" + } + } + }, + "NameIdentifier": { + "description": "Identifier by either name or ID. Note that a name may change due to renaming, or reused after being deleted. ID is immutable and unique.", + "type": "object", + "properties": { + "Id": { + "description": "Id Identifier, if present", + "type": "string" + }, + "Name": { + "description": "Name Identifier, if present", + "type": "string" + } + }, + "example": { + "Name": "MyTask", + "Id": "d739e188-22f6-4d13-a4d5-df2e93d4d2f7" + } + }, + "PlayerChurnPredictionSegmentFilter": { + "type": "object", + "properties": { + "Comparison": { + "$ref": "#/definitions/SegmentFilterComparison", + "description": "Comparison", + "type": "string", + "x-isenum": true + }, + "RiskLevel": { + "$ref": "#/definitions/ChurnRiskLevel", + "description": "RiskLevel", + "type": "string", + "x-isenum": true + } + } + }, + "PlayerChurnPredictionTimeSegmentFilter": { + "type": "object", + "properties": { + "Comparison": { + "$ref": "#/definitions/SegmentFilterComparison", + "description": "Comparison", + "type": "string", + "x-isenum": true + }, + "DurationInDays": { + "description": "DurationInDays", + "type": "number", + "x-actualtype": "double" + } + }, + "required": [ + "DurationInDays" + ] + }, + "PlayerChurnPreviousPredictionSegmentFilter": { + "type": "object", + "properties": { + "Comparison": { + "$ref": "#/definitions/SegmentFilterComparison", + "description": "Comparison", + "type": "string", + "x-isenum": true + }, + "RiskLevel": { + "$ref": "#/definitions/ChurnRiskLevel", + "description": "RiskLevel", + "type": "string", + "x-isenum": true + } + } + }, + "PlayerLinkedAccount": { + "type": "object", + "properties": { + "Email": { + "description": "Linked account's email", + "type": "string" + }, + "Platform": { + "$ref": "#/definitions/LoginIdentityProvider", + "description": "Authentication platform", + "type": "string", + "x-isenum": true + }, + "PlatformUserId": { + "description": "Platform user identifier", + "type": "string" + }, + "Username": { + "description": "Linked account's username", + "type": "string" + } + } + }, + "PlayerLocation": { + "type": "object", + "properties": { + "City": { + "description": "City of the player's geographic location.", + "type": "string" + }, + "ContinentCode": { + "$ref": "#/definitions/ContinentCode", + "description": "The two-character continent code for this location", + "type": "string", + "x-isenum": true + }, + "CountryCode": { + "$ref": "#/definitions/CountryCode", + "description": "The two-character ISO 3166-1 country code for the country associated with the location", + "type": "string", + "x-isenum": true + }, + "Latitude": { + "description": "Latitude coordinate of the player's geographic location.", + "type": "number", + "x-actualtype": "double" + }, + "Longitude": { + "description": "Longitude coordinate of the player's geographic location.", + "type": "number", + "x-actualtype": "double" + } + }, + "required": [ + "ContinentCode", + "CountryCode" + ] + }, + "PlayerStatistic": { + "type": "object", + "properties": { + "Id": { + "description": "Statistic ID", + "type": "string" + }, + "Name": { + "description": "Statistic name", + "type": "string" + }, + "StatisticValue": { + "description": "Current statistic value", + "type": "number", + "x-actualtype": "int32" + }, + "StatisticVersion": { + "description": "Statistic version (0 if not a versioned statistic)", + "type": "number", + "x-actualtype": "int32" + } + }, + "required": [ + "StatisticVersion", + "StatisticValue" + ] + }, + "PlayerStatisticDefinition": { + "type": "object", + "properties": { + "AggregationMethod": { + "$ref": "#/definitions/StatisticAggregationMethod", + "description": "the aggregation method to use in updating the statistic (defaults to last)", + "type": "string", + "x-isenum": true + }, + "CurrentVersion": { + "description": "current active version of the statistic, incremented each time the statistic resets", + "type": "number", + "x-actualtype": "uint32" + }, + "StatisticName": { + "description": "unique name of the statistic", + "type": "string" + }, + "VersionChangeInterval": { + "$ref": "#/definitions/StatisticResetIntervalOption", + "description": "interval at which the values of the statistic for all players are reset automatically", + "type": "string", + "x-isenum": true + } + }, + "required": [ + "CurrentVersion" + ], + "example": { + "StatisticName": "headshots", + "CurrentVersion": 2, + "VersionChangeInterval": "Day" + } + }, + "PlayerStatisticVersion": { + "type": "object", + "properties": { + "ActivationTime": { + "description": "time when the statistic version became active", + "type": "string" + }, + "ArchiveDownloadUrl": { + "description": "URL for the downloadable archive of player statistic values, if available", + "type": "string" + }, + "DeactivationTime": { + "description": "time when the statistic version became inactive due to statistic version incrementing", + "type": "string" + }, + "ScheduledActivationTime": { + "description": "time at which the statistic version was scheduled to become active, based on the configured ResetInterval", + "type": "string" + }, + "ScheduledDeactivationTime": { + "description": "time at which the statistic version was scheduled to become inactive, based on the configured ResetInterval", + "type": "string" + }, + "StatisticName": { + "description": "name of the statistic when the version became active", + "type": "string" + }, + "Status": { + "$ref": "#/definitions/StatisticVersionStatus", + "description": "status of the statistic version", + "type": "string", + "x-isenum": true + }, + "Version": { + "description": "version of the statistic", + "type": "number", + "x-actualtype": "uint32" + } + }, + "required": [ + "Version", + "ActivationTime" + ], + "example": { + "StatisticName": "headshots", + "Version": 2, + "ActivationTime": "2016-12-05T10:02:01", + "Status": "Archived", + "ArchiveDownloadUrl": "https://Someurl" + } + }, + "PushNotificationPlatform": { + "type": "string", + "enum": [ + "ApplePushNotificationService", + "GoogleCloudMessaging" + ] + }, + "PushNotificationRegistration": { + "type": "object", + "properties": { + "NotificationEndpointARN": { + "description": "Notification configured endpoint", + "type": "string" + }, + "Platform": { + "$ref": "#/definitions/PushNotificationPlatform", + "description": "Push notification platform", + "type": "string", + "x-isenum": true + } + } + }, + "PushNotificationRegistrationModel": { + "type": "object", + "properties": { + "NotificationEndpointARN": { + "description": "Notification configured endpoint", + "type": "string" + }, + "Platform": { + "$ref": "#/definitions/PushNotificationPlatform", + "description": "Push notification platform", + "type": "string", + "x-isenum": true + } + } + }, + "PushNotificationSegmentAction": { + "type": "object", + "properties": { + "PushNotificationTemplateId": { + "description": "Push notification template id.", + "type": "string" + } + } + }, + "PushNotificationSegmentFilter": { + "type": "object", + "properties": { + "PushNotificationDevicePlatform": { + "$ref": "#/definitions/SegmentPushNotificationDevicePlatform", + "description": "Push notification device platform.", + "type": "string", + "x-isenum": true + } + } + }, + "SegmentAndDefinition": { + "type": "object", + "properties": { + "AdCampaignFilter": { + "$ref": "#/definitions/AdCampaignSegmentFilter", + "description": "Filter property for ad campaign filter.", + "type": "object", + "x-isclass": true + }, + "AllPlayersFilter": { + "$ref": "#/definitions/AllPlayersSegmentFilter", + "description": "property for all player filter.", + "type": "object", + "x-isclass": true + }, + "ChurnPredictionFilter": { + "$ref": "#/definitions/ChurnPredictionSegmentFilter", + "description": "Filter property for player churn risk level.", + "type": "object", + "x-isclass": true + }, + "FirstLoginDateFilter": { + "$ref": "#/definitions/FirstLoginDateSegmentFilter", + "description": "Filter property for first login date.", + "type": "object", + "x-isclass": true + }, + "FirstLoginFilter": { + "$ref": "#/definitions/FirstLoginTimespanSegmentFilter", + "description": "Filter property for first login timespan.", + "type": "object", + "x-isclass": true + }, + "LastLoginDateFilter": { + "$ref": "#/definitions/LastLoginDateSegmentFilter", + "description": "Filter property for last login date.", + "type": "object", + "x-isclass": true + }, + "LastLoginFilter": { + "$ref": "#/definitions/LastLoginTimespanSegmentFilter", + "description": "Filter property for last login timespan.", + "type": "object", + "x-isclass": true + }, + "LinkedUserAccountFilter": { + "$ref": "#/definitions/LinkedUserAccountSegmentFilter", + "description": "Filter property for linked in user account.", + "type": "object", + "x-isclass": true + }, + "LinkedUserAccountHasEmailFilter": { + "$ref": "#/definitions/LinkedUserAccountHasEmailSegmentFilter", + "description": "Filter property for linked in user account has email.", + "type": "object", + "x-isclass": true + }, + "LocationFilter": { + "$ref": "#/definitions/LocationSegmentFilter", + "description": "Filter property for location.", + "type": "object", + "x-isclass": true + }, + "PlayerChurnPredictionFilter": { + "$ref": "#/definitions/PlayerChurnPredictionSegmentFilter", + "description": "Filter property for current player churn value.", + "type": "object", + "x-isclass": true + }, + "PlayerChurnPredictionTimeFilter": { + "$ref": "#/definitions/PlayerChurnPredictionTimeSegmentFilter", + "description": "Filter property for player churn timespan.", + "type": "object", + "x-isclass": true + }, + "PlayerChurnPreviousPredictionFilter": { + "$ref": "#/definitions/PlayerChurnPreviousPredictionSegmentFilter", + "description": "Filter property for previous player churn value.", + "type": "object", + "x-isclass": true + }, + "PushNotificationFilter": { + "$ref": "#/definitions/PushNotificationSegmentFilter", + "description": "Filter property for push notification.", + "type": "object", + "x-isclass": true + }, + "StatisticFilter": { + "$ref": "#/definitions/StatisticSegmentFilter", + "description": "Filter property for statistics.", + "type": "object", + "x-isclass": true + }, + "TagFilter": { + "$ref": "#/definitions/TagSegmentFilter", + "description": "Filter property for tags.", + "type": "object", + "x-isclass": true + }, + "TotalValueToDateInUSDFilter": { + "$ref": "#/definitions/TotalValueToDateInUSDSegmentFilter", + "description": "Filter property for total value to date in USD.", + "type": "object", + "x-isclass": true + }, + "UserOriginationFilter": { + "$ref": "#/definitions/UserOriginationSegmentFilter", + "description": "Filter property for user origination.", + "type": "object", + "x-isclass": true + }, + "ValueToDateFilter": { + "$ref": "#/definitions/ValueToDateSegmentFilter", + "description": "Filter property for value to date.", + "type": "object", + "x-isclass": true + }, + "VirtualCurrencyBalanceFilter": { + "$ref": "#/definitions/VirtualCurrencyBalanceSegmentFilter", + "description": "Filter property for virtual currency.", + "type": "object", + "x-isclass": true + } + } + }, + "SegmentCountryCode": { + "type": "string", + "enum": [ + "AF", + "AX", + "AL", + "DZ", + "AS", + "AD", + "AO", + "AI", + "AQ", + "AG", + "AR", + "AM", + "AW", + "AU", + "AT", + "AZ", + "BS", + "BH", + "BD", + "BB", + "BY", + "BE", + "BZ", + "BJ", + "BM", + "BT", + "BO", + "BQ", + "BA", + "BW", + "BV", + "BR", + "IO", + "BN", + "BG", + "BF", + "BI", + "KH", + "CM", + "CA", + "CV", + "KY", + "CF", + "TD", + "CL", + "CN", + "CX", + "CC", + "CO", + "KM", + "CG", + "CD", + "CK", + "CR", + "CI", + "HR", + "CU", + "CW", + "CY", + "CZ", + "DK", + "DJ", + "DM", + "DO", + "EC", + "EG", + "SV", + "GQ", + "ER", + "EE", + "ET", + "FK", + "FO", + "FJ", + "FI", + "FR", + "GF", + "PF", + "TF", + "GA", + "GM", + "GE", + "DE", + "GH", + "GI", + "GR", + "GL", + "GD", + "GP", + "GU", + "GT", + "GG", + "GN", + "GW", + "GY", + "HT", + "HM", + "VA", + "HN", + "HK", + "HU", + "IS", + "IN", + "ID", + "IR", + "IQ", + "IE", + "IM", + "IL", + "IT", + "JM", + "JP", + "JE", + "JO", + "KZ", + "KE", + "KI", + "KP", + "KR", + "KW", + "KG", + "LA", + "LV", + "LB", + "LS", + "LR", + "LY", + "LI", + "LT", + "LU", + "MO", + "MK", + "MG", + "MW", + "MY", + "MV", + "ML", + "MT", + "MH", + "MQ", + "MR", + "MU", + "YT", + "MX", + "FM", + "MD", + "MC", + "MN", + "ME", + "MS", + "MA", + "MZ", + "MM", + "NA", + "NR", + "NP", + "NL", + "NC", + "NZ", + "NI", + "NE", + "NG", + "NU", + "NF", + "MP", + "NO", + "OM", + "PK", + "PW", + "PS", + "PA", + "PG", + "PY", + "PE", + "PH", + "PN", + "PL", + "PT", + "PR", + "QA", + "RE", + "RO", + "RU", + "RW", + "BL", + "SH", + "KN", + "LC", + "MF", + "PM", + "VC", + "WS", + "SM", + "ST", + "SA", + "SN", + "RS", + "SC", + "SL", + "SG", + "SX", + "SK", + "SI", + "SB", + "SO", + "ZA", + "GS", + "SS", + "ES", + "LK", + "SD", + "SR", + "SJ", + "SZ", + "SE", + "CH", + "SY", + "TW", + "TJ", + "TZ", + "TH", + "TL", + "TG", + "TK", + "TO", + "TT", + "TN", + "TR", + "TM", + "TC", + "TV", + "UG", + "UA", + "AE", + "GB", + "US", + "UM", + "UY", + "UZ", + "VU", + "VE", + "VN", + "VG", + "VI", + "WF", + "EH", + "YE", + "ZM", + "ZW" + ] + }, + "SegmentCurrency": { + "type": "string", + "enum": [ + "AED", + "AFN", + "ALL", + "AMD", + "ANG", + "AOA", + "ARS", + "AUD", + "AWG", + "AZN", + "BAM", + "BBD", + "BDT", + "BGN", + "BHD", + "BIF", + "BMD", + "BND", + "BOB", + "BRL", + "BSD", + "BTN", + "BWP", + "BYR", + "BZD", + "CAD", + "CDF", + "CHF", + "CLP", + "CNY", + "COP", + "CRC", + "CUC", + "CUP", + "CVE", + "CZK", + "DJF", + "DKK", + "DOP", + "DZD", + "EGP", + "ERN", + "ETB", + "EUR", + "FJD", + "FKP", + "GBP", + "GEL", + "GGP", + "GHS", + "GIP", + "GMD", + "GNF", + "GTQ", + "GYD", + "HKD", + "HNL", + "HRK", + "HTG", + "HUF", + "IDR", + "ILS", + "IMP", + "INR", + "IQD", + "IRR", + "ISK", + "JEP", + "JMD", + "JOD", + "JPY", + "KES", + "KGS", + "KHR", + "KMF", + "KPW", + "KRW", + "KWD", + "KYD", + "KZT", + "LAK", + "LBP", + "LKR", + "LRD", + "LSL", + "LYD", + "MAD", + "MDL", + "MGA", + "MKD", + "MMK", + "MNT", + "MOP", + "MRO", + "MUR", + "MVR", + "MWK", + "MXN", + "MYR", + "MZN", + "NAD", + "NGN", + "NIO", + "NOK", + "NPR", + "NZD", + "OMR", + "PAB", + "PEN", + "PGK", + "PHP", + "PKR", + "PLN", + "PYG", + "QAR", + "RON", + "RSD", + "RUB", + "RWF", + "SAR", + "SBD", + "SCR", + "SDG", + "SEK", + "SGD", + "SHP", + "SLL", + "SOS", + "SPL", + "SRD", + "STD", + "SVC", + "SYP", + "SZL", + "THB", + "TJS", + "TMT", + "TND", + "TOP", + "TRY", + "TTD", + "TVD", + "TWD", + "TZS", + "UAH", + "UGX", + "USD", + "UYU", + "UZS", + "VEF", + "VND", + "VUV", + "WST", + "XAF", + "XCD", + "XDR", + "XOF", + "XPF", + "YER", + "ZAR", + "ZMW", + "ZWD" + ] + }, + "SegmentFilterComparison": { + "type": "string", + "enum": [ + "GreaterThan", + "LessThan", + "EqualTo", + "NotEqualTo", + "GreaterThanOrEqual", + "LessThanOrEqual", + "Exists", + "Contains", + "NotContains" + ] + }, + "SegmentLoginIdentityProvider": { + "type": "string", + "enum": [ + "Unknown", + "PlayFab", + "Custom", + "GameCenter", + "GooglePlay", + "Steam", + "XBoxLive", + "PSN", + "Kongregate", + "Facebook", + "IOSDevice", + "AndroidDevice", + "Twitch", + "WindowsHello", + "GameServer", + "CustomServer", + "NintendoSwitch", + "FacebookInstantGames", + "OpenIdConnect", + "Apple", + "NintendoSwitchAccount" + ] + }, + "SegmentModel": { + "type": "object", + "properties": { + "Description": { + "description": "Segment description.", + "type": "string" + }, + "EnteredSegmentActions": { + "description": "Segment actions for current entered segment players.", + "type": "array", + "items": { + "$ref": "#/definitions/SegmentTrigger" + }, + "x-isclass": true + }, + "LastUpdateTime": { + "description": "Segment last updated date time.", + "type": "string" + }, + "LeftSegmentActions": { + "description": "Segment actions for current left segment players.", + "type": "array", + "items": { + "$ref": "#/definitions/SegmentTrigger" + }, + "x-isclass": true + }, + "Name": { + "description": "Segment name.", + "type": "string" + }, + "SegmentId": { + "description": "Segment id in hex.", + "type": "string" + }, + "SegmentOrDefinitions": { + "description": "Segment or definitions. This includes segment and definitions and filters.", + "type": "array", + "items": { + "$ref": "#/definitions/SegmentOrDefinition" + }, + "x-isclass": true + } + }, + "required": [ + "LastUpdateTime" + ] + }, + "SegmentOrDefinition": { + "type": "object", + "properties": { + "SegmentAndDefinitions": { + "description": "List of segment and definitions.", + "type": "array", + "items": { + "$ref": "#/definitions/SegmentAndDefinition" + }, + "x-isclass": true + } + } + }, + "SegmentPushNotificationDevicePlatform": { + "type": "string", + "enum": [ + "ApplePushNotificationService", + "GoogleCloudMessaging" + ] + }, + "SegmentTrigger": { + "type": "object", + "properties": { + "BanPlayerAction": { + "$ref": "#/definitions/BanPlayerSegmentAction", + "description": "Ban player segment trigger action.", + "type": "object", + "x-isclass": true + }, + "DeletePlayerAction": { + "$ref": "#/definitions/DeletePlayerSegmentAction", + "description": "Delete player segment trigger action.", + "type": "object", + "x-isclass": true + }, + "DeletePlayerStatisticAction": { + "$ref": "#/definitions/DeletePlayerStatisticSegmentAction", + "description": "Delete player statistic segment trigger action.", + "type": "object", + "x-isclass": true + }, + "EmailNotificationAction": { + "$ref": "#/definitions/EmailNotificationSegmentAction", + "description": "Email notification segment trigger action.", + "type": "object", + "x-isclass": true + }, + "ExecuteAzureFunctionAction": { + "$ref": "#/definitions/ExecuteAzureFunctionSegmentAction", + "description": "Execute azure function segment trigger action.", + "type": "object", + "x-isclass": true + }, + "ExecuteCloudScriptAction": { + "$ref": "#/definitions/ExecuteCloudScriptSegmentAction", + "description": "Execute cloud script segment trigger action.", + "type": "object", + "x-isclass": true + }, + "GrantItemAction": { + "$ref": "#/definitions/GrantItemSegmentAction", + "description": "Grant item segment trigger action.", + "type": "object", + "x-isclass": true + }, + "GrantVirtualCurrencyAction": { + "$ref": "#/definitions/GrantVirtualCurrencySegmentAction", + "description": "Grant virtual currency segment trigger action.", + "type": "object", + "x-isclass": true + }, + "IncrementPlayerStatisticAction": { + "$ref": "#/definitions/IncrementPlayerStatisticSegmentAction", + "description": "Increment player statistic segment trigger action.", + "type": "object", + "x-isclass": true + }, + "PushNotificationAction": { + "$ref": "#/definitions/PushNotificationSegmentAction", + "description": "Push notification segment trigger action.", + "type": "object", + "x-isclass": true + } + } + }, + "StatisticAggregationMethod": { + "type": "string", + "enum": [ + "Last", + "Min", + "Max", + "Sum" + ] + }, + "StatisticModel": { + "type": "object", + "properties": { + "Name": { + "description": "Statistic name", + "type": "string" + }, + "Value": { + "description": "Statistic value", + "type": "number", + "x-actualtype": "int32" + }, + "Version": { + "description": "Statistic version (0 if not a versioned statistic)", + "type": "number", + "x-actualtype": "int32" + } + }, + "required": [ + "Version", + "Value" + ] + }, + "StatisticResetIntervalOption": { + "type": "string", + "enum": [ + "Never", + "Hour", + "Day", + "Week", + "Month" + ] + }, + "StatisticSegmentFilter": { + "type": "object", + "properties": { + "Comparison": { + "$ref": "#/definitions/SegmentFilterComparison", + "description": "Statistic filter comparison.", + "type": "string", + "x-isenum": true + }, + "FilterValue": { + "description": "Statistic filter value.", + "type": "string" + }, + "Name": { + "description": "Statistic name.", + "type": "string" + }, + "UseCurrentVersion": { + "description": "Use current version of statistic?", + "type": "boolean" + }, + "Version": { + "description": "Statistic version.", + "type": "number", + "x-actualtype": "int32" + } + } + }, + "StatisticVersionStatus": { + "type": "string", + "enum": [ + "Active", + "SnapshotPending", + "Snapshot", + "ArchivalPending", + "Archived" + ] + }, + "TagModel": { + "type": "object", + "properties": { + "TagValue": { + "description": "Full value of the tag, including namespace", + "type": "string" + } + } + }, + "TagSegmentFilter": { + "type": "object", + "properties": { + "Comparison": { + "$ref": "#/definitions/SegmentFilterComparison", + "description": "Tag comparison.", + "type": "string", + "x-isenum": true + }, + "TagValue": { + "description": "Tag value.", + "type": "string" + } + } + }, + "TotalValueToDateInUSDSegmentFilter": { + "type": "object", + "properties": { + "Amount": { + "description": "Total value to date USD amount.", + "type": "string" + }, + "Comparison": { + "$ref": "#/definitions/SegmentFilterComparison", + "description": "Total value to date USD comparison.", + "type": "string", + "x-isenum": true + } + } + }, + "UserOrigination": { + "type": "string", + "enum": [ + "Organic", + "Steam", + "Google", + "Amazon", + "Facebook", + "Kongregate", + "GamersFirst", + "Unknown", + "IOS", + "LoadTest", + "Android", + "PSN", + "GameCenter", + "CustomId", + "XboxLive", + "Parse", + "Twitch", + "ServerCustomId", + "NintendoSwitchDeviceId", + "FacebookInstantGamesId", + "OpenIdConnect", + "Apple", + "NintendoSwitchAccount", + "GooglePlayGames" + ] + }, + "UserOriginationSegmentFilter": { + "type": "object", + "properties": { + "LoginProvider": { + "$ref": "#/definitions/SegmentLoginIdentityProvider", + "description": "User login provider.", + "type": "string", + "x-isenum": true + } + } + }, + "ValueToDateModel": { + "type": "object", + "properties": { + "Currency": { + "description": "ISO 4217 code of the currency used in the purchases", + "type": "string" + }, + "TotalValue": { + "description": "Total value of the purchases in a whole number of 1/100 monetary units. For example, 999 indicates nine dollars and ninety-nine cents when Currency is 'USD')", + "type": "number", + "x-actualtype": "uint32" + }, + "TotalValueAsDecimal": { + "description": "Total value of the purchases in a string representation of decimal monetary units. For example, '9.99' indicates nine dollars and ninety-nine cents when Currency is 'USD'.", + "type": "string" + } + }, + "required": [ + "TotalValue" + ] + }, + "ValueToDateSegmentFilter": { + "type": "object", + "properties": { + "Amount": { + "description": "Value to date amount.", + "type": "string" + }, + "Comparison": { + "$ref": "#/definitions/SegmentFilterComparison", + "description": "Value to date comparison.", + "type": "string", + "x-isenum": true + }, + "Currency": { + "$ref": "#/definitions/SegmentCurrency", + "description": "Currency using for filter.", + "type": "string", + "x-isenum": true + } + } + }, + "VirtualCurrencyBalanceSegmentFilter": { + "type": "object", + "properties": { + "Amount": { + "description": "Total amount.", + "type": "number", + "x-actualtype": "int32" + }, + "Comparison": { + "$ref": "#/definitions/SegmentFilterComparison", + "description": "Amount comparison.", + "type": "string", + "x-isenum": true + }, + "CurrencyCode": { + "description": "Currency code.", + "type": "string" + } + }, + "required": [ + "Amount" + ] + }, + "VirtualCurrencyData": { + "type": "object", + "properties": { + "CurrencyCode": { + "description": "unique two-character identifier for this currency type (e.g.: \"CC\")", + "type": "string" + }, + "DisplayName": { + "description": "friendly name to show in the developer portal, reports, etc.", + "type": "string" + }, + "InitialDeposit": { + "description": "amount to automatically grant users upon first login to the title", + "type": "number", + "x-actualtype": "int32" + }, + "RechargeMax": { + "description": "maximum amount to which the currency will recharge (cannot exceed MaxAmount, but can be less)", + "type": "number", + "x-actualtype": "int32" + }, + "RechargeRate": { + "description": "rate at which the currency automatically be added to over time, in units per day (24 hours)", + "type": "number", + "x-actualtype": "int32" + } + }, + "required": [ + "CurrencyCode" + ], + "example": { + "CurrencyCode": "GO", + "DisplayName": "Gold", + "InitialDeposit": 0 + } + } + }, + "parameters": { + "CreateSegmentRequest": { + "name": "CreateSegmentRequest", + "in": "body", + "description": "Send all the segment details part of CreateSegmentRequest", + "schema": { + "$ref": "#/definitions/CreateSegmentRequest" + } + }, + "DeleteSegmentRequest": { + "name": "DeleteSegmentRequest", + "in": "body", + "description": "Send segment id planning to delete part of DeleteSegmentRequest object", + "schema": { + "$ref": "#/definitions/DeleteSegmentRequest" + } + }, + "GetDataReportRequest": { + "name": "GetDataReportRequest", + "in": "body", + "description": "Gets the download URL for the requested report data (in CSV form). The reports available through this API call are those available in the Game Manager, in the Analytics->Reports tab.", + "schema": { + "$ref": "#/definitions/GetDataReportRequest" + } + }, + "GetPlayerStatisticDefinitionsRequest": { + "name": "GetPlayerStatisticDefinitionsRequest", + "in": "body", + "schema": { + "$ref": "#/definitions/GetPlayerStatisticDefinitionsRequest" + } + }, + "GetPlayerStatisticVersionsRequest": { + "name": "GetPlayerStatisticVersionsRequest", + "in": "body", + "schema": { + "$ref": "#/definitions/GetPlayerStatisticVersionsRequest" + } + }, + "GetSegmentsRequest": { + "name": "GetSegmentsRequest", + "in": "body", + "description": "Given input segment ids, return list of segments.", + "schema": { + "$ref": "#/definitions/GetSegmentsRequest" + } + }, + }, + "responses": { + "ApiErrorWrapper": { + "description": "This is the outer wrapper for all responses with errors", + "schema": { + "$ref": "#/definitions/ApiErrorWrapper" + } + }, + "CreateSegmentResponse": { + "description": "", + "schema": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "description": "The Http status code. If X-ReportErrorAsSuccess header is set to true, this will report the actual http error code." + }, + "status": { + "type": "string", + "description": "The Http status code as a string." + }, + "data": { + "$ref": "#/definitions/CreateSegmentResponse" + } + } + } + }, + "DeleteSegmentsResponse": { + "description": "", + "schema": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "description": "The Http status code. If X-ReportErrorAsSuccess header is set to true, this will report the actual http error code." + }, + "status": { + "type": "string", + "description": "The Http status code as a string." + }, + "data": { + "$ref": "#/definitions/DeleteSegmentsResponse" + } + } + } + }, + "EmptyResponse": { + "description": "", + "schema": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "description": "The Http status code. If X-ReportErrorAsSuccess header is set to true, this will report the actual http error code." + }, + "status": { + "type": "string", + "description": "The Http status code as a string." + }, + "data": { + "$ref": "#/definitions/EmptyResponse" + } + } + } + }, + "GetDataReportResult": { + "description": "", + "schema": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "description": "The Http status code. If X-ReportErrorAsSuccess header is set to true, this will report the actual http error code." + }, + "status": { + "type": "string", + "description": "The Http status code as a string." + }, + "data": { + "$ref": "#/definitions/GetDataReportResult" + } + }, + "example": { + "code": 200, + "status": "OK", + "data": { + "DownloadUrl": "https://data-reports-dev.s3-us-west-1.amazonaws.com/2730/PurchaseDataReport/2014/12/5/data.json?AWSAccessKeyId=AKIAJB5VEITH4PHABPAA&Expires=1418151409&Signature=1cF0Xre4FETLW%2BIu5BCW0TL8ShQ%3D" + } + } + } + }, + "GetPlayerStatisticDefinitionsResult": { + "description": "Statistics are numeric values, with each statistic in the title also generating a leaderboard. The ResetInterval defines the period of time at which the leaderboard for the statistic will automatically reset. Upon reset, the statistic updates to a new version with no values (effectively removing all players from the leaderboard). The previous version's statistic values are also archived for retrieval, if needed (see GetPlayerStatisticVersions). Statistics not created via a call to CreatePlayerStatisticDefinition by default have a VersionChangeInterval of Never, meaning they do not reset on a schedule, but they can be set to do so via a call to UpdatePlayerStatisticDefinition. Once a statistic has been reset (sometimes referred to as versioned or incremented), the previous version can still be written to for up a short, pre-defined period (currently 10 seconds), to prevent issues with levels completing around the time of the reset. Also, once reset, the historical statistics for players in the title may be retrieved using the URL specified in the version information (GetPlayerStatisticVersions). The AggregationMethod defines what action is taken when a new statistic value is submitted - always update with the new value (Last), use the highest of the old and new values (Max), use the smallest (Min), or add them together (Sum).", + "schema": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "description": "The Http status code. If X-ReportErrorAsSuccess header is set to true, this will report the actual http error code." + }, + "status": { + "type": "string", + "description": "The Http status code as a string." + }, + "data": { + "$ref": "#/definitions/GetPlayerStatisticDefinitionsResult" + } + }, + "example": { + "code": 200, + "status": "OK", + "data": { + "Statistics": [ + { + "StatisticName": "headshots", + "CurrentVersion": 2, + "VersionChangeInterval": "Day" + } + ] + } + } + } + }, + "GetPlayerStatisticVersionsResult": { + "description": "Statistics are numeric values, with each statistic in the title also generating a leaderboard. The information returned in the results defines the state of a specific version of a statistic, including when it was or will become the currently active version, when it will (or did) become a previous version, and its archival state if it is no longer the active version. For a statistic which has been reset, once the archival status is Complete, the full set of statistics for all players in the leaderboard for that version may be retrieved via the ArchiveDownloadUrl. Statistics which have not been reset (incremented/versioned) will only have a single version which is not scheduled to reset.", + "schema": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "description": "The Http status code. If X-ReportErrorAsSuccess header is set to true, this will report the actual http error code." + }, + "status": { + "type": "string", + "description": "The Http status code as a string." + }, + "data": { + "$ref": "#/definitions/GetPlayerStatisticVersionsResult" + } + }, + "example": { + "code": 200, + "status": "OK", + "data": { + "StatisticVersions": [ + { + "StatisticName": "headshots", + "Version": 2, + "ActivationTime": "2016-12-05T10:02:01", + "Status": "Archived", + "ArchiveDownloadUrl": "https://Someurl" + } + ] + } + } + } + }, + "GetSegmentsResponse": { + "description": "", + "schema": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "description": "The Http status code. If X-ReportErrorAsSuccess header is set to true, this will report the actual http error code." + }, + "status": { + "type": "string", + "description": "The Http status code as a string." + }, + "data": { + "$ref": "#/definitions/GetSegmentsResponse" + } + } + } + } + }, + "tags": [ + { + "name": "Player Data Management", + "description": "Player Data Management APIs" + }, + { + "name": "Segments", + "description": "Segment management APIs" + } + ], + "securityDefinitions": { + "None": { + "type": "apiKey", + "in": "header", + "name": "None", + "description": "This API requires no authentication headers (usually provides one to other calls)." + }, + "SessionTicket": { + "type": "apiKey", + "in": "header", + "name": "X-Authorization", + "description": "This API requires a client session ticket, available from any Client Login function." + }, + "SecretKey": { + "type": "apiKey", + "in": "header", + "name": "X-SecretKey", + "description": "This API requires a title secret key, available to title admins, from PlayFab Game Manager." + }, + "EntityToken": { + "type": "apiKey", + "in": "header", + "name": "X-EntityToken", + "description": "This API requires an Entity Session Token, available from the Entity GetEntityToken method." + } + }, + "x-ms-docs-errors-mapping": { + "Success": { + "name": "Success" + }, + "UnkownError": { + "id": 500, + "name": "UnkownError" + }, + "InvalidParams": { + "id": 1000, + "name": "InvalidParams" + }, + "UnknownError": { + "id": 1039, + "name": "UnknownError" + }, + "InvalidSegment": { + "id": 1242, + "name": "InvalidSegment" + }, + "TitleNotOnUpdatedPricingPlan": { + "id": 9000, + "name": "TitleNotOnUpdatedPricingPlan" + }, + "SegmentManagementTitleNotInFlight": { + "id": 10000, + "name": "SegmentManagementTitleNotInFlight" + }, + "SegmentManagementNoExpressionTree": { + "id": 10001, + "name": "SegmentManagementNoExpressionTree" + }, + "SegmentManagementTriggerActionCountOverLimit": { + "id": 10002, + "name": "SegmentManagementTriggerActionCountOverLimit" + }, + "SegmentManagementSegmentCountOverLimit": { + "id": 10003, + "name": "SegmentManagementSegmentCountOverLimit" + }, + "SegmentManagementInvalidSegmentId": { + "id": 10004, + "name": "SegmentManagementInvalidSegmentId" + }, + "SegmentManagementInvalidInput": { + "id": 10005, + "name": "SegmentManagementInvalidInput" + }, + "SegmentManagementInvalidSegmentName": { + "id": 10006, + "name": "SegmentManagementInvalidSegmentName" + }, + "DeleteSegmentRateLimitExceeded": { + "id": 10007, + "name": "DeleteSegmentRateLimitExceeded" + }, + "CreateSegmentRateLimitExceeded": { + "id": 10008, + "name": "CreateSegmentRateLimitExceeded" + }, + "GetSegmentsRateLimitExceeded": { + "id": 10010, + "name": "GetSegmentsRateLimitExceeded" + }, + "AnalyticsSegmentCountOverLimit": { + "id": 10014, + "name": "AnalyticsSegmentCountOverLimit" + }, + "OperationCanceled": { + "id": 16000, + "name": "OperationCanceled" + } + } +} \ No newline at end of file diff --git a/dotnet/samples/KernelSyntaxExamples/TestConfiguration.cs b/dotnet/samples/KernelSyntaxExamples/TestConfiguration.cs index 1c2ff6f60078..861356972603 100644 --- a/dotnet/samples/KernelSyntaxExamples/TestConfiguration.cs +++ b/dotnet/samples/KernelSyntaxExamples/TestConfiguration.cs @@ -23,6 +23,7 @@ public static void Initialize(IConfigurationRoot configRoot) public static OpenAIConfig OpenAI => LoadSection(); public static AzureOpenAIConfig AzureOpenAI => LoadSection(); public static AzureOpenAIEmbeddingsConfig AzureOpenAIEmbeddings => LoadSection(); + public static AzureOpenAITextDavinciConfig AzureOpenAITextDavinci => LoadSection(); public static ACSConfig ACS => LoadSection(); public static QdrantConfig Qdrant => LoadSection(); public static WeaviateConfig Weaviate => LoadSection(); @@ -36,6 +37,7 @@ public static void Initialize(IConfigurationRoot configRoot) public static RedisConfig Redis => LoadSection(); public static JiraConfig Jira => LoadSection(); public static ChromaConfig Chroma => LoadSection(); + public static PlayFabConfig PlayFab => LoadSection(); private static T LoadSection([CallerMemberName] string? caller = null) { @@ -78,6 +80,13 @@ public class AzureOpenAIEmbeddingsConfig public string ApiKey { get; set; } } + public class AzureOpenAITextDavinciConfig + { + public string DeploymentName { get; set; } + public string Endpoint { get; set; } + public string ApiKey { get; set; } + } + public class ACSConfig { public string Endpoint { get; set; } @@ -154,5 +163,18 @@ public class ChromaConfig { public string Endpoint { get; set; } } + + public class PlayFabConfig + { + public string Endpoint { get; set; } + public string TitleId { get; set; } + public string TitleSecretKey { get; set; } + public string SwaggerEndpoint { get; set; } + public string ReportsCosmosDBEndpoint { get; set; } + public string ReportsCosmosDBKey { get; set; } + public string ReportsCosmosDBDatabaseName { get; set; } + public string ReportsCosmosDBContainerName { get; set; } + } + #pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. } diff --git a/dotnet/samples/PlayFabExamples/Common/Configuration/ConfigurationNotFoundException.cs b/dotnet/samples/PlayFabExamples/Common/Configuration/ConfigurationNotFoundException.cs new file mode 100644 index 000000000000..4de877dad55a --- /dev/null +++ b/dotnet/samples/PlayFabExamples/Common/Configuration/ConfigurationNotFoundException.cs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace PlayFabExamples.Common.Configuration; +public sealed class ConfigurationNotFoundException : Exception +{ + public string? Section { get; } + public string? Key { get; } + + public ConfigurationNotFoundException(string section, string key) + : base($"Configuration key '{section}:{key}' not found") + { + this.Section = section; + this.Key = key; + } + + public ConfigurationNotFoundException(string section) + : base($"Configuration section '{section}' not found") + { + this.Section = section; + } + + public ConfigurationNotFoundException() : base() + { + } + + public ConfigurationNotFoundException(string? message, Exception? innerException) : base(message, innerException) + { + } +} diff --git a/dotnet/samples/PlayFabExamples/Common/Configuration/Env.cs b/dotnet/samples/PlayFabExamples/Common/Configuration/Env.cs new file mode 100644 index 000000000000..5bf07044b76d --- /dev/null +++ b/dotnet/samples/PlayFabExamples/Common/Configuration/Env.cs @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Microsoft.Extensions.Configuration; + +namespace PlayFabExamples.Common.Configuration; + +#pragma warning disable CA1812 // instantiated by AddUserSecrets +internal sealed class Env +#pragma warning restore CA1812 +{ + /// + /// Simple helper used to load env vars and secrets like credentials, + /// to avoid hard coding them in the sample code + /// + /// Secret name / Env var name + /// Value found in Secret Manager or Environment Variable + internal static string Var(string name) + { + var configuration = new ConfigurationBuilder() + .AddUserSecrets() + .Build(); + + var value = configuration[name]; + if (!string.IsNullOrEmpty(value)) + { + return value; + } + + value = Environment.GetEnvironmentVariable(name); + if (string.IsNullOrEmpty(value)) + { + throw new ConfigurationNotFoundException($"Secret / Env var not set: {name}"); + } + + return value; + } +} diff --git a/dotnet/samples/PlayFabExamples/Common/Configuration/TestConfiguration.cs b/dotnet/samples/PlayFabExamples/Common/Configuration/TestConfiguration.cs new file mode 100644 index 000000000000..75c9fd314e3d --- /dev/null +++ b/dotnet/samples/PlayFabExamples/Common/Configuration/TestConfiguration.cs @@ -0,0 +1,72 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Runtime.CompilerServices; +using Microsoft.Extensions.Configuration; + +namespace PlayFabExamples.Common.Configuration; +public sealed class TestConfiguration +{ + private IConfigurationRoot _configRoot; + private static TestConfiguration? s_instance; + + private TestConfiguration(IConfigurationRoot configRoot) + { + this._configRoot = configRoot; + } + + public static void Initialize(IConfigurationRoot configRoot) + { + s_instance = new TestConfiguration(configRoot); + } + + public static AzureOpenAIConfig AzureOpenAI => LoadSection(); + + public static BingConfig Bing => LoadSection(); + public static PlayFabConfig PlayFab => LoadSection(); + + private static T LoadSection([CallerMemberName] string? caller = null) + { + if (s_instance == null) + { + throw new InvalidOperationException( + "TestConfiguration must be initialized with a call to Initialize(IConfigurationRoot) before accessing configuration values."); + } + + if (string.IsNullOrEmpty(caller)) + { + throw new ArgumentNullException(nameof(caller)); + } + return s_instance._configRoot.GetSection(caller).Get() ?? + throw new ConfigurationNotFoundException(section: caller); + } + +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. + + public class AzureOpenAIConfig + { + public string ServiceId { get; set; } + public string DeploymentName { get; set; } + public string ChatDeploymentName { get; set; } + public string Endpoint { get; set; } + public string ApiKey { get; set; } + } + + public class BingConfig + { + public string ApiKey { get; set; } + } + + public class PlayFabConfig + { + public string Endpoint { get; set; } + public string TitleId { get; set; } + public string TitleSecretKey { get; set; } + public string SwaggerEndpoint { get; set; } + public string ReportsCosmosDBEndpoint { get; set; } + public string ReportsCosmosDBKey { get; set; } + public string ReportsCosmosDBDatabaseName { get; set; } + public string ReportsCosmosDBContainerName { get; set; } + } + +#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. +} diff --git a/dotnet/samples/PlayFabExamples/Common/Logging/ConsoleLogger.cs b/dotnet/samples/PlayFabExamples/Common/Logging/ConsoleLogger.cs new file mode 100644 index 000000000000..ca47e2a71750 --- /dev/null +++ b/dotnet/samples/PlayFabExamples/Common/Logging/ConsoleLogger.cs @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Microsoft.Extensions.Logging; + +namespace PlayFabExamples.Common.Logging; +/// +/// Basic logger printing to console +/// +internal static class ConsoleLogger +{ + internal static ILogger Logger => LogFactory.CreateLogger(); + + private static ILoggerFactory LogFactory => s_loggerFactory.Value; + + private static readonly Lazy s_loggerFactory = new(LogBuilder); + + private static ILoggerFactory LogBuilder() + { + return LoggerFactory.Create(builder => + { + builder.SetMinimumLevel(LogLevel.Warning); + + // builder.AddFilter("Microsoft", LogLevel.Trace); + // builder.AddFilter("Microsoft", LogLevel.Debug); + // builder.AddFilter("Microsoft", LogLevel.Information); + // builder.AddFilter("Microsoft", LogLevel.Warning); + // builder.AddFilter("Microsoft", LogLevel.Error); + + builder.AddFilter("Microsoft", LogLevel.Warning); + builder.AddFilter("System", LogLevel.Warning); + + builder.AddConsole(); + }); + } +} diff --git a/dotnet/samples/PlayFabExamples/Example01_DataQnA/Example01_DataQnA.cs b/dotnet/samples/PlayFabExamples/Example01_DataQnA/Example01_DataQnA.cs new file mode 100644 index 000000000000..26bbc41b394d --- /dev/null +++ b/dotnet/samples/PlayFabExamples/Example01_DataQnA/Example01_DataQnA.cs @@ -0,0 +1,745 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.ComponentModel; +using System.Diagnostics; +using System.Text; +using Azure.AI.OpenAI; +using Azure; +using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.Memory; +using Microsoft.SemanticKernel.Orchestration; +using Microsoft.SemanticKernel.Planning; +using Microsoft.SemanticKernel.Reliability; +using Microsoft.SemanticKernel.SkillDefinition; +using Microsoft.Azure.Cosmos; +using Newtonsoft.Json.Linq; +using Newtonsoft.Json; +using PlayFabExamples.Common.Configuration; +using PlayFabExamples.Common.Logging; + +namespace PlayFabExamples.Example01_DataQnA; + +public enum PlannerType +{ + Stepwise, + ChatStepwise, + SimpleAction +} + +// ReSharper disable once InconsistentNaming +public static partial class Example01_DataQnA +{ + public static Dictionary AllTitleReports = null; + + public static async Task RunAsync() + { + CancellationToken cancellationToken = CancellationToken.None; + string[] questions = new string[] + { + "What is my 2-days retention average? Was my 2-days retention in the last few days was better or worse than that?", + "How many players played my game yesterday?", + "What is the average number of players I had last week excluding Friday and Monday?", + "Is my game doing better in USA or in China?", + "If the number of monthly active players in France increases by 30%, what would be the percentage increase to the overall monthly active players?", + "At which specific times of the day were the highest and lowest numbers of purchases recorded? Please provide the actual sales figures for these particular time slots.", + "Which three items had the highest total sales and which had the highest revenue generated yesterday?", + }; + + PlannerType[] planners = new[] + { + // PlannerType.Stepwise, + // PlannerType.ChatStepwise, + PlannerType.SimpleAction + }; + + foreach (string question in questions) + { + foreach (PlannerType planner in planners) + { + await Console.Out.WriteLineAsync("--------------------------------------------------------------------------------------------------------------------"); + await Console.Out.WriteLineAsync("Planner: " + planner); + await Console.Out.WriteLineAsync("Question: " + question); + await Console.Out.WriteLineAsync("--------------------------------------------------------------------------------------------------------------------"); + + IKernel kernel = await GetKernelAsync(cancellationToken); + + try + { + await RunWithQuestionAsync(kernel, question, PlannerType.SimpleAction, cancellationToken); + } + catch (Exception e) + { + Console.WriteLine(e); + } + } + } + } + + private static async Task RunWithQuestionAsync(IKernel kernel, string question, PlannerType plannerType, CancellationToken cancellationToken) + { + kernel.ImportSkill(new InlineDataProcessorSkill(kernel.Memory), "InlineDataProcessor"); + + // Maybe with gpt4 we can add more skills and make them more granular. Planners are instable with Gpt3.5 and complex analytic stesps. + // kernel.ImportSkill(new GameReportFetcherSkill(kernel.Memory), "GameReportFetcher"); + // kernel.ImportSkill(new LanguageCalculatorSkill(kernel), "advancedCalculator"); + // var bingConnector = new BingConnector(TestConfiguration.Bing.ApiKey); + // var webSearchEngineSkill = new WebSearchEngineSkill(bingConnector); + // kernel.ImportSkill(webSearchEngineSkill, "WebSearch"); + // kernel.ImportSkill(new SimpleCalculatorSkill(kernel), "basicCalculator"); + // kernel.ImportSkill(new TimeSkill(), "time"); + + Plan plan; + Stopwatch sw = Stopwatch.StartNew(); + if (plannerType == PlannerType.SimpleAction) + { + var planner = new ActionPlanner(kernel); + plan = await planner.CreatePlanAsync(question, cancellationToken); + } + else if (plannerType == PlannerType.ChatStepwise) + { + var plannerConfig = new Microsoft.SemanticKernel.Planning.Stepwise.StepwisePlannerConfig(); + plannerConfig.ExcludedFunctions.Add("TranslateMathProblem"); + plannerConfig.MinIterationTimeMs = 1500; + plannerConfig.MaxTokens = 12000; + + ChatStepwisePlanner planner = new(kernel, plannerConfig); + + plan = planner.CreatePlan(question); + } + else if (plannerType == PlannerType.Stepwise) + { + var plannerConfig = new Microsoft.SemanticKernel.Planning.Stepwise.StepwisePlannerConfig(); + plannerConfig.ExcludedFunctions.Add("TranslateMathProblem"); + plannerConfig.MinIterationTimeMs = 1500; + plannerConfig.MaxTokens = 1500; + + StepwisePlanner planner = new(kernel, plannerConfig); + + plan = planner.CreatePlan(question); + } + else + { + throw new NotSupportedException($"[{plannerType}] Planner type is not supported."); + } + + SKContext result = await plan.InvokeAsync(kernel.CreateNewContext(), cancellationToken: cancellationToken); + Console.WriteLine("Result: " + result); + if (result.Variables.TryGetValue("stepCount", out string? stepCount)) + { + Console.WriteLine("Steps Taken: " + stepCount); + } + + if (result.Variables.TryGetValue("skillCount", out string? skillCount)) + { + Console.WriteLine("Skills Used: " + skillCount); + } + + Console.WriteLine("Time Taken: " + sw.Elapsed); + Console.WriteLine(""); + } + + private static async Task GetKernelAsync(CancellationToken cancellationToken) + { + var builder = new KernelBuilder(); + + if (!string.IsNullOrEmpty(TestConfiguration.AzureOpenAI.ChatDeploymentName)) + { + builder = builder.WithAzureChatCompletionService( + TestConfiguration.AzureOpenAI.ChatDeploymentName, + TestConfiguration.AzureOpenAI.Endpoint, + TestConfiguration.AzureOpenAI.ApiKey, + alsoAsTextCompletion: true, + setAsDefault: true); + } + + if (!string.IsNullOrEmpty(TestConfiguration.AzureOpenAI.DeploymentName)) + { + builder = builder.WithAzureTextCompletionService( + TestConfiguration.AzureOpenAI.DeploymentName, + TestConfiguration.AzureOpenAI.Endpoint, + TestConfiguration.AzureOpenAI.ApiKey); + } + + var kernel = builder + .WithLogger(ConsoleLogger.Logger) + .WithAzureTextEmbeddingGenerationService( + deploymentName: "text-embedding-ada-002", + endpoint: TestConfiguration.AzureOpenAI.Endpoint, + apiKey: TestConfiguration.AzureOpenAI.ApiKey) + .WithMemoryStorage(new VolatileMemoryStore()) + .Configure(c => c.SetDefaultHttpRetryConfig(new HttpRetryConfig + { + MaxRetryCount = 3, + UseExponentialBackoff = true, + MinRetryDelay = TimeSpan.FromSeconds(3), + })) + .Build(); + + // We're using volotile memory, so pre-load it with data + await InitializeMemoryAsync(kernel, TestConfiguration.PlayFab.TitleId, cancellationToken); + + return kernel; + } + + private static async Task InitializeMemoryAsync(IKernel kernel, string titleId, CancellationToken cancellationToken) + { + if (AllTitleReports == null) + { + DateTime today = DateTime.UtcNow.Date; + + using var reportDataFetcher = new ReportDataFetcher( + titleId, + TestConfiguration.PlayFab.ReportsCosmosDBEndpoint, + TestConfiguration.PlayFab.ReportsCosmosDBKey, + TestConfiguration.PlayFab.ReportsCosmosDBDatabaseName, + TestConfiguration.PlayFab.ReportsCosmosDBContainerName); + + IList gameReports = await reportDataFetcher.FetchByQueryAsync( + $"SELECT * FROM c WHERE c.TitleId='{titleId}' and c.ReportDate>='{today.AddDays(-3):yyyy-MM-dd}'", + cancellationToken); + + Dictionary latestReports = gameReports + .GroupBy(report => report.ReportName) + .Select(group => group.OrderByDescending(report => report.ReportDate).First()) + .ToDictionary(report => report.ReportName); + + if (!latestReports.ContainsKey("EngagementMetricsRollupReportCSV")) + { + GameReport gameEngagementRollupReport = (await reportDataFetcher.FetchByQueryAsync( + $"SELECT TOP 1 * FROM c WHERE c.TitleId='{titleId}' and c.ReportName='EngagementMetricsRollupReportCSV' ORDER BY c.ReportDate DESC", + cancellationToken)).FirstOrDefault(); + latestReports.Add(gameEngagementRollupReport.ReportName, gameEngagementRollupReport); + } + + string dailyOverviewReport = + DailyOverviewReportRecord.GetDescription() + Environment.NewLine + + DailyOverviewReportRecord.GetHeader() + Environment.NewLine + + string.Join(Environment.NewLine, + JsonConvert.DeserializeObject>(latestReports["DailyOverviewReport"].ReportData) + .Select(reportData => reportData.AsCsvRow())); + + string rollingThirtyDayOverviewReport = + RollingThirtyDayOverviewReportRecord.GetDescription() + Environment.NewLine + + RollingThirtyDayOverviewReportRecord.GetHeader() + Environment.NewLine + + string.Join(Environment.NewLine, + JsonConvert.DeserializeObject>(latestReports["RollingThirtyDayOverviewReport"].ReportData) + .Select(reportData => reportData.AsCsvRow())); + + string dailyTopItemsReportRecord = + DailyTopItemsReportRecord.GetDescription() + Environment.NewLine + + DailyTopItemsReportRecord.GetHeader() + Environment.NewLine + + string.Join(Environment.NewLine, + JsonConvert.DeserializeObject>(latestReports["DailyTopItemsReport"].ReportData) + .Select(reportData => reportData.AsCsvRow())); + + string thirtyDayRetentionReportRecord = + ThirtyDayRetentionReportRecord.GetDescription() + Environment.NewLine + + ThirtyDayRetentionReportRecord.GetHeader() + Environment.NewLine + + string.Join(Environment.NewLine, + JsonConvert.DeserializeObject>(latestReports["ThirtyDayRetentionReport"].ReportData) + .Where(row => row.CohortDate > DateTime.UtcNow.AddDays(-14)) // Filter just last 14 days so it doesn't pass the embedding limit when indexed + .Select(reportData => reportData.AsCsvRow())); + + var latestEngagementMetrics = + latestReports["EngagementMetricsRollupReportCSV"].ReportData + .Split("\"", StringSplitOptions.RemoveEmptyEntries).Where(line => line != ",") + .Select(line => + { + string[] values = line.Split(','); + + return new EngagementMetricsRollupReportCSVRecord + { + ReportDate = DateTime.Parse(values[1]), + Platform = values[2], + Region = values[3], + Segment = values[4], + MonthlyActiveUsers = int.Parse(values[5]), + DailyActiveUsers = int.Parse(values[6]), + NewPlayers = int.Parse(values[7]), + Retention1Day = double.Parse(values[11]), + Retention7Day = double.Parse(values[12]), + }; + }) + .Where(row => row.Segment == "All" && row.Platform == "All") + .ToList(); + + DateTime latestEngagmementRolloutReportDate = latestEngagementMetrics.Max(row => row.ReportDate); + latestEngagementMetrics = latestEngagementMetrics.Where(row => row.ReportDate == latestEngagmementRolloutReportDate).ToList(); + string engagementMetricsRollupReport = + EngagementMetricsRollupReportCSVRecord.GetDescription() + Environment.NewLine + + EngagementMetricsRollupReportCSVRecord.GetHeader() + Environment.NewLine + + string.Join(Environment.NewLine, latestEngagementMetrics.Select(row => row.AsCsvRow())); + + AllTitleReports = new Dictionary(); + AllTitleReports.Add("DailyOverviewReport", dailyOverviewReport); + AllTitleReports.Add("RollingThirtyDayOverviewReport", rollingThirtyDayOverviewReport); + AllTitleReports.Add("DailyTopItemsReportRecord", dailyTopItemsReportRecord); + AllTitleReports.Add("ThirtyDayRetentionReportRecord", thirtyDayRetentionReportRecord); + AllTitleReports.Add("EngagementMetricsRollupReport", engagementMetricsRollupReport); + } + + foreach (var item in AllTitleReports) + { + await kernel.Memory.SaveInformationAsync( + collection: "TitleID-Reports", + text: item.Value, + id: item.Key, + cancellationToken: cancellationToken); + } + } +} + +public class InlineDataProcessorSkill +{ + #region Static Data + /// + /// The system prompt for a chat that creates python scripts to solve analytic problems + /// + private static readonly string CreatePythonScriptSystemPrompt = @" +You're a python script programmer. +Once you get a question, write a Python script that loads the comma-separated (CSV) data inline (within the script) into a dataframe. +The CSV data should not be assumed to be available in any external file. +Only load the data from [Input CSV]. do not attempt to initialize the data frame with any additional rows. +The script should: +- Attempt to answer the provided question and print the output to the console as a user-friendly answer. +- Print facts and calculations that lead to this answer +- Import any necessary modules within the script (e.g., import datetime if used) +- If the script needs to use StringIO, it should import io, and then use it as io.StringIO (To avoid this error: module 'pandas.compat' has no attribute 'StringIO') +The script can use one or more of the provided inline scripts and should favor the ones relevant to the question. + +Simply output the final script below without anything beside the code and its inline documentation. +Never attempt to calculate the MonthlyActiveUsers as a sum of DailyActiveUsers since DailyActiveUsers only gurantees the user uniqueness within a single day + +[Input CSV] +{{$inlineData}} + +"; + + /// + /// The user agent prompt for fixing a python script that has runtime errors + /// + private static readonly string FixPythonScriptPrompt = @" +The following python error has encountered while running the script above. +Fix the script so it has no errors. +Make the minimum changes that are required. If you need to use StringIO, make sure to import io, and then use it as io.StringIO +simply output the final script below without any additional explanations. + +[Error] +{{$error}} + +[Fixed Script] +"; + #endregion + + #region Data Members + /// + /// The semantic memory containing relevant reports needed to solve the provided question + /// + private readonly ISemanticTextMemory _memory; + + /// + /// An open AI client + /// + private readonly OpenAIClient _openAIClient; + #endregion + + #region Constructor + /// + /// Constructor + /// + /// The semantic memory containing relevant reports needed to solve the provided question + public InlineDataProcessorSkill(ISemanticTextMemory memory) + { + this._memory = memory ?? throw new ArgumentNullException(nameof(memory)); + _openAIClient = new( + new Uri(TestConfiguration.AzureOpenAI.Endpoint), + new AzureKeyCredential(TestConfiguration.AzureOpenAI.ApiKey)); + } + #endregion + + #region Public Methods + [SKFunction, + SKName("GetAnswerForGameQuestion"), + Description("Answers questions about game's data and its players around engagement, usage, time spent and game analytics")] + public async Task GetAnswerForGameQuestionAsync( + [Description("The question related to the provided inline data.")] + string question, + SKContext context) + { + StringBuilder stringBuilder = new(); + var memories = _memory.SearchAsync("TitleID-Reports", question, limit: 2, minRelevanceScore: 0.65); + int idx = 1; + await foreach (MemoryQueryResult memory in memories) + { + stringBuilder.AppendLine($"[Input CSV {idx++}]"); + stringBuilder.AppendLine(memory.Metadata.Text); + stringBuilder.AppendLine(); + } + + string csvData = stringBuilder.ToString(); + string ret = await CreateAndExcecutePythonScript(question, csvData); + return ret; + } + #endregion + + #region Private Methods + /// + /// Creates and executes a python script to get an answer for an analytic question + /// + /// The analytic question + /// The data that can be used to answer the given question (e.g: can be list of CSV reports) + /// The final answer + private async Task CreateAndExcecutePythonScript(string question, string inlineData) + { + DateTime today = DateTime.UtcNow; + + var chatCompletion = new ChatCompletionsOptions() + { + Messages = + { + new ChatMessage(ChatRole.System, CreatePythonScriptSystemPrompt.Replace("{{$inlineData}}", inlineData)), + new ChatMessage(ChatRole.User, question + "\nPrint facts and calculations that lead to this answer\n[Python Script]") + }, + Temperature = 0.1f, + MaxTokens = 8000, + NucleusSamplingFactor = 1f, + FrequencyPenalty = 0, + PresencePenalty = 0, + }; + + Azure.Response response = await this._openAIClient.GetChatCompletionsAsync( + deploymentOrModelName: TestConfiguration.AzureOpenAI.ChatDeploymentName, chatCompletion); + + // Path to the Python executable + string pythonPath = "python"; // Use "python3" if on a Unix-like system + + int retry = 0; + while (retry++ < 3) + { + // Inline Python script + string pythonScript = response.Value.Choices[0].Message.Content; + pythonScript = GetScriptOnly(pythonScript) + .Replace("\"", "\\\"") // Quote so we can run python via commandline + .Replace("pd.compat.StringIO(", "io.StringIO("); // Fix common script mistake + + if (!pythonScript.Contains("import io")) + { + pythonScript = "import io\n\n" + pythonScript; + } + + // Create a ProcessStartInfo and set the required properties + var startInfo = new ProcessStartInfo + { + FileName = pythonPath, + Arguments = "-c \"" + pythonScript + "\"", + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true + }; + + // Create a new Process + using Process process = new() { StartInfo = startInfo }; + + // Start the Python process + process.Start(); + + // Read the Python process output and error + string output = process.StandardOutput.ReadToEnd().Trim(); + string warningsAndErrors = process.StandardError.ReadToEnd().Trim(); + + + // Wait for the process to finish + process.WaitForExit(); + + // If there are errors in the script, try to fix them + if (process.ExitCode != 0) + { + Console.WriteLine("Error in script: " + warningsAndErrors); + chatCompletion.Messages.Add(new ChatMessage(ChatRole.Assistant, pythonScript)); + chatCompletion.Messages.Add(new ChatMessage(ChatRole.User, FixPythonScriptPrompt.Replace("{{$error}}", warningsAndErrors))); + + response = await this._openAIClient.GetChatCompletionsAsync( + deploymentOrModelName: TestConfiguration.AzureOpenAI.ChatDeploymentName, chatCompletion); + } + else + { + return output; + } + } + + return "Couldn't get an answer"; + } + + private string GetScriptOnly(string pythonScript) + { + const string startMarker = "```python"; + const string endMarker = "```"; + + string ret = pythonScript; + int startIndex = pythonScript.IndexOf(startMarker) + startMarker.Length; + int endIndex = pythonScript.LastIndexOf(endMarker); + + if (startIndex >= 0 && endIndex > startIndex) + { + ret = pythonScript.Substring(startIndex, endIndex - startIndex); + } + + return ret; + } + + #endregion +} + +public class ReportDataFetcher : IDisposable +{ + private readonly string _titleId; + private readonly CosmosClient _cosmosClient; + private readonly string _databaseName; + private readonly string _containerName; + + public ReportDataFetcher(string titleId, string endpointUrl, string primaryKey, string databaseName, string containerName) + { + this._titleId = titleId ?? throw new ArgumentNullException(nameof(titleId)); + this._cosmosClient = new( + endpointUrl ?? throw new ArgumentNullException(nameof(endpointUrl)), + primaryKey ?? throw new ArgumentNullException(nameof(primaryKey))); + + this._databaseName = databaseName ?? throw new ArgumentNullException(nameof(databaseName)); + this._containerName = containerName ?? throw new ArgumentNullException(nameof(containerName)); + } + + public async Task FetchAsync(string documentId, CancellationToken cancellationToken) + { + // Get a reference to the database and container + Database database = this._cosmosClient.GetDatabase(this._databaseName); + Microsoft.Azure.Cosmos.Container container = database.GetContainer(this._containerName); + + // Read the document by its ID + ItemResponse response = await container.ReadItemAsync(documentId, new PartitionKey(this._titleId), cancellationToken: cancellationToken); + return response.Resource; + } + + public async Task> FetchByQueryAsync(string query, CancellationToken cancellationToken) + { + // Get a reference to the database and container + Database database = this._cosmosClient.GetDatabase(this._databaseName); + Microsoft.Azure.Cosmos.Container container = database.GetContainer(this._containerName); + + List ret = new(); + + // Execute the query + QueryDefinition queryDefinition = new(query); + FeedIterator resultSetIterator = container.GetItemQueryIterator(queryDefinition); + + while (resultSetIterator.HasMoreResults) + { + FeedResponse response = await resultSetIterator.ReadNextAsync(cancellationToken); + foreach (dynamic item in response) + { + GameReport? gameReport = ((JObject)item).ToObject(); + if (gameReport is not null) + { + ret.Add(gameReport); + } + } + } + + return ret; + } + + public void Dispose() + { + this._cosmosClient.Dispose(); + } +} + +public class GameReport +{ + public DateTime ReportDate { get; set; } + public required string ReportId { get; set; } + public required string ReportName { get; set; } + public required string ReportData { get; set; } +} + +public class DailyOverviewReportRecord +{ + public static string GetHeader() => + "Timestamp,TotalLogins,UniqueLogins,UniquePayers,Revenue,Purchases,TotalCalls,TotalSuccessfulCalls,TotalErrors,Arpu,Arppu,AvgPurchasePrice,NewUsers"; + + public string AsCsvRow() => + $"{this.Timestamp},{this.TotalLogins},{this.UniqueLogins},{this.UniquePayers},{this.Revenue},{this.Purchases},{this.TotalCalls},{this.TotalSuccessfulCalls},{this.TotalErrors},{this.Arpu},{this.Arppu},{this.AvgPurchasePrice},{this.NewUsers}"; + + public static string GetDescription() => + """ + The provided CSV table contains granular daily data capturing game reports for each hour, offering valuable insights into player engagement, financial performance, and the overall gameplay experience. + This dataset offers a comprehensive view into player behavior, enabling data-driven decisions to enhance gameplay, optimize monetization strategies, and improve overall player satisfaction. + Through its hour-by-hour breakdown, it allows for precise analysis of temporal patterns, aiding in understanding player dynamics over different times of the day. + The report has 24 rows where every row reprsents one hour of the day. + Description of Columns: + - Timestamp: The date and time of a one-hour window when the report was compiled, presented in Coordinated Universal Time (UTC). + - TotalLogins: The aggregate count of player logins during the specified hour, revealing the volume of player interactions. + - UniqueLogins: The distinct number of players who logged into the game within the same hour, indicating individual engagement. + - UniquePayers: The count of unique players who conducted in-game purchases, reflecting the game's monetization reach. + - Revenue: The cumulative revenue in dollars generated from in-game purchases throughout the hour, demonstrating financial performance. + - Purchases: The total number of in-game transactions carried out by players in the specified hour. + - TotalCalls: The collective sum of player-initiated interactions, encompassing gameplay actions, API requests, and more.. + - TotalSuccessfulCalls: The count of interactions that succeeded without encountering errors, highlighting player satisfaction. + - TotalErrors: The overall number of errors encountered during interactions, potential indicators of player experience challenges. + - Arpu (Average Revenue Per User): The average revenue generated per unique player, calculated as Revenue / UniquePayers. + - Arppu (Average Revenue Per Paying User): The average revenue generated per player who made purchases, calculated as Revenue / UniquePayers. + - AvgPurchasePrice: The average price of in-game purchases made by players, calculated as Revenue / Purchases. + - NewUsers: The count of new players who started engaging with the game during the specified hour period. + """; + + public DateTime Timestamp { get; set; } + public int TotalLogins { get; set; } + public int UniqueLogins { get; set; } + public int UniquePayers { get; set; } + public decimal Revenue { get; set; } + public int Purchases { get; set; } + public long TotalCalls { get; set; } + public long TotalSuccessfulCalls { get; set; } + public long TotalErrors { get; set; } + public decimal Arpu { get; set; } + public decimal Arppu { get; set; } + public decimal AvgPurchasePrice { get; set; } + public int NewUsers { get; set; } +} + +public class RollingThirtyDayOverviewReportRecord +{ + public static string GetHeader() => + "Timestamp,TotalLogins,UniqueLogins,UniquePayers,Revenue,Purchases,TotalCalls,TotalSuccessfulCalls,TotalErrors,Arpu,Arppu,AvgPurchasePrice,NewUsers"; + + public string AsCsvRow() => + $"{this.Timestamp:yyyy/MM/dd},{this.TotalLogins},{this.UniqueLogins},{this.UniquePayers},{this.Revenue},{this.Purchases},{this.TotalCalls},{this.TotalSuccessfulCalls},{this.TotalErrors},{this.Arpu},{this.Arppu},{this.AvgPurchasePrice},{this.NewUsers}"; + + public static string GetDescription() => + """ + The provided CSV table contains daily data for the last 30 days capturing game reports for each day, offering valuable insights into player engagement, financial performance, and the overall gameplay experience. + This dataset offers a comprehensive view into player behavior, enabling data-driven decisions to enhance gameplay, optimize monetization strategies, and improve overall player satisfaction. + Through its day-by-day breakdown, it allows for precise analysis of temporal patterns, aiding in understanding player dynamics over different times of the week. + The report has 30 rows where every row reprsents one the day of the last 30 days. + Description of Columns: + - Timestamp: The date of a one-day window when the report was compiled, presented in Coordinated Universal Time (UTC). + - TotalLogins: The aggregate count of player logins during the specified day, revealing the volume of player interactions. + - UniqueLogins: The distinct number of players who logged into the game within the same day, indicating individual engagement. + - UniquePayers: The count of unique players who conducted in-game purchases, reflecting the game's monetization reach. + - Revenue: The cumulative revenue in dollars generated from in-game purchases throughout the day, demonstrating financial performance. + - Purchases: The total number of in-game transactions carried out by players in the specified day. + - TotalCalls: The collective sum of player-initiated interactions, encompassing gameplay actions, API requests, and more.. + - TotalSuccessfulCalls: The count of interactions that succeeded without encountering errors, highlighting player satisfaction. + - TotalErrors: The overall number of errors encountered during interactions, potential indicators of player experience challenges. + - Arpu (Average Revenue Per User): The average revenue generated per unique player, calculated as Revenue / UniquePayers. + - Arppu (Average Revenue Per Paying User): The average revenue generated per player who made purchases, calculated as Revenue / UniquePayers. + - AvgPurchasePrice: The average price of in-game purchases made by players, calculated as Revenue / Purchases. + - NewUsers: The count of new players who started engaging with the game during the specified day period. + """; + public DateTime Timestamp { get; set; } + public int TotalLogins { get; set; } + public int UniqueLogins { get; set; } + public int UniquePayers { get; set; } + public decimal Revenue { get; set; } + public int Purchases { get; set; } + public long TotalCalls { get; set; } + public long TotalSuccessfulCalls { get; set; } + public long TotalErrors { get; set; } + public decimal Arpu { get; set; } + public decimal Arppu { get; set; } + public decimal AvgPurchasePrice { get; set; } + public int NewUsers { get; set; } +} + +public class DailyTopItemsReportRecord +{ + public static string GetHeader() => "ItemName,TotalSales,TotalRevenue"; + + public string AsCsvRow() => + $"{this.ItemName.Replace("[\"", "").Replace("\"]", "")},{this.TotalSales},{this.TotalRevenue}"; + + public static string GetDescription() => + """ + This dataset presents a thorough view of sales reports for a single day, capturing vital details about specific product performance and revenue generation. + Each entry sheds light on sales figures per product, helping decision-makers enhance strategies for growth. + The dataset is valuable for gauging product popularity, revenue trends, and customer engagement. + The provided data empowers data-driven decision-making and supports efforts to enhance product offerings and optimize sales strategies for sustained success + Description of Columns: + - ItemName: The name of the product, representing a distinct item available for purchase. + - TotalSales: The cumulative count of sales for the specific item, indicating its popularity and market demand. + - TotalRevenue: The total monetary value of revenue generated from sales of the item in US dollars. + """; + + public string ItemName { get; set; } + public int TotalSales { get; set; } + public decimal TotalRevenue { get; set; } +} + +public class EngagementMetricsRollupReportCSVRecord +{ + public static string GetHeader() => "Date,Region,MonthlyActiveUsers,DailyActiveUsers,NewPlayers,Retention1Day,Retention7Day"; + + public string AsCsvRow() => + $"{this.ReportDate:yyyy/MM/dd},{this.Region},{this.MonthlyActiveUsers},{this.DailyActiveUsers},{this.NewPlayers},{this.Retention1Day},{this.Retention7Day}"; + + public static string GetDescription() => + """ + The provided CSV table contains weekly aggregated data related to the user activity and retention for a gaming application. + This data is broken down by different geographic regions, including France, Greater China, Japan, United Kingdom, United States, Latin America, India, Middle East & Africa, Germany, Canada, Western Europe, Asia Pacific, and Central & Eastern Europe. + Each row represents a different geographic regions, and the columns contain specific metrics related to user engagement. + There is a special row for each week with the Region set to 'All', which means this row aggregates data across all the regions for that week + Below is the description of each field in the table: + - ReportDate: The date for week for which the data is recorded + - Region: The geographic region to which the data pertains.Examples include Greater China, France, Japan, United Kingdom, United States, Latin America, India, Middle East & Africa, Germany, Canada, Western Europe, Asia Pacific, and Central & Eastern Europe. + - MonthlyActiveUsers: The total number of unique users who engaged with the game at least once during the month + - DailyActiveUsers: The total number of unique users who engaged with the game on that week. + - NewPlayers: The number of new users who joined and engaged with the game on that week. + - Retention1Day: The percentage of users who returned to the game on the day after their first engagement. + - Retention7Day: The percentage of users who returned to the game seven days after their first engagement. + """; + + public DateTime ReportDate { get; set; } + public string Platform { get; set; } + public string Region { get; set; } + public string Segment { get; set; } + public long MonthlyActiveUsers { get; set; } + public long DailyActiveUsers { get; set; } + public long NewPlayers { get; set; } + public double FocusAverageDuration { get; set; } + public double FocusAveragePeriodsPerUser { get; set; } + public long FocusTotalPeriods { get; set; } + public double Retention1Day { get; set; } + public double Retention7Day { get; set; } + public double Retention14Day { get; set; } + public double Retention30Day { get; set; } + public double RevenueDollars { get; set; } +} + +public class ThirtyDayRetentionReportRecord +{ + public static string GetHeader() => "CohortDate,CohortSize,DaysLater,TotalRetained,PercentRetained"; + + public string AsCsvRow() => + $"{this.CohortDate:yyyy/MM/dd},{this.CohortSize},{this.DaysLater},{this.TotalRetained},{this.PercentRetained}"; + + public static string GetDescription() => + """ + The provided CSV table contains retention report for daily cohorts of players in the last 30 days. + This retention dataset helps analyze player engagement and the effectiveness of retention strategies by tracking player behavior over time periods. + It can offer insights into player retention rates, allowing game developers to make informed decisions to improve player engagement and overall game experience. + Below is the description of each field in the table: + - CohortDate: The timestamp indicating when the retention data was collected + - CohortSize: The initial size of the cohort, representing the number of players at the beginning of the retention period. + - DaysLater: The number of days later at which the retention is being measured. + - TotalRetained: The total number of players retained in the specified cohort after the specified number of days. + - PercentRetained: The percentage of players retained in the cohort after the specified number of days. + """; + + [JsonProperty("Ts")] + public DateTime CohortDate { get; set; } + public int CohortSize { get; set; } + [JsonProperty("PeriodsLater")] + public int DaysLater { get; set; } + public int TotalRetained { get; set; } + public double PercentRetained { get; set; } +} diff --git a/dotnet/samples/PlayFabExamples/Example02_Generative/Example02_Generative.cs b/dotnet/samples/PlayFabExamples/Example02_Generative/Example02_Generative.cs new file mode 100644 index 000000000000..f685f9ba535f --- /dev/null +++ b/dotnet/samples/PlayFabExamples/Example02_Generative/Example02_Generative.cs @@ -0,0 +1,68 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.Orchestration; +using Microsoft.SemanticKernel.Planning; +using PlayFabExamples.Common.Configuration; +using PlayFabExamples.Common.Logging; + +namespace PlayFabExamples.Example02_Generative; +public static class Example02_Generative +{ + public static async Task RunAsync() + { + var goals = new string[] + { + "Create a segment with name NewPlayersSegment for the players first logged in date greater than 2023-08-01?", // Working + "Create a segment with name LegacyPlayersSegment for the players last logged in date less than 2023-05-01?", // Working + "Create a segment with name EgyptNewPlayers for the players located in the Egypt?", // Working + "Create a segment with name ChinaPlayers for the players in china and grant them 10 VC virtual currency?", // Working + //"Create a segment with name ChinaNewPlayers for the players in china who first logged in the last 30 days and grant them 10 virtual currency?", + //"Create a segment with name WelcomeEgyptNewPlayers for the players located in the Egypt with entered segment action of email notification?", // With entered segment action + //"Create a segment with name EgyptNewPlayers for the players located in the Egypt?" // If the segment already exist, create a segment with name appended with guid + }; + + foreach (string prompt in goals) + { + try + { + await CreateSegmentExample(prompt); + } + catch (Exception ex) + { + Console.WriteLine(ex.ToString()); + } + } + } + + private static async Task CreateSegmentExample(string goal) + { + // Create a segment skill + Console.WriteLine("======== Action Planner ========"); + var kernel = new KernelBuilder() + .WithLogger(ConsoleLogger.Logger) + .WithAzureTextCompletionService(TestConfiguration.AzureOpenAI.DeploymentName, TestConfiguration.AzureOpenAI.Endpoint, TestConfiguration.AzureOpenAI.ApiKey) + .Build(); + + kernel.ImportSkill(new SegmentSkill(), "SegmentSkill"); + + // Create an instance of ActionPlanner. + // The ActionPlanner takes one goal and returns a single function to execute. + var planner = new ActionPlanner(kernel); + + // We're going to ask the planner to find a function to achieve this goal. + //var goal = "Create a segment with name NewPlayersSegment for the players first logged in date greater than 2023-08-01?"; + Console.WriteLine("Goal: " + goal); + + // The planner returns a plan, consisting of a single function + // to execute and achieve the goal requested. + var plan = await planner.CreatePlanAsync(goal); + plan.Steps[0].Parameters = plan.Parameters; + + // Execute the full plan (which is a single function) + SKContext result = await plan.InvokeAsync(kernel.CreateNewContext()); + + // Show the result, which should match the given goal + Console.WriteLine(result); + } +} diff --git a/dotnet/samples/PlayFabExamples/Example02_Generative/SegmentSkill.cs b/dotnet/samples/PlayFabExamples/Example02_Generative/SegmentSkill.cs new file mode 100644 index 000000000000..d9771cac2b1b --- /dev/null +++ b/dotnet/samples/PlayFabExamples/Example02_Generative/SegmentSkill.cs @@ -0,0 +1,113 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.ComponentModel; +using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.Orchestration; +using Microsoft.SemanticKernel.SkillDefinition; +using Microsoft.SemanticKernel.Skills.OpenAPI.Authentication; +using Microsoft.SemanticKernel.Skills.OpenAPI.Extensions; +using Newtonsoft.Json; +using PlayFabExamples.Common.Configuration; +using PlayFabExamples.Common.Logging; + +namespace PlayFabExamples.Example02_Generative; + +/// +/// Create a segment with given information. +/// +public sealed class SegmentSkill +{ + /// + /// Read a file + /// + /// + /// {{file.readAsync $path }} => "hello world" + /// + /// Source file + /// File content + [SKFunction, Description("Create a segment using prompt and parsing prompt")] + public async Task CreateSegment([Description("Name of the segment.")] string segmentname, + [Description("Name of the segment definition. Some of the examples are FirstLoginDateFilter, LastLoginDateFilter, LocationFilter.")] string segmentdefinition, + [Description("Name of the segment comparison. Some of the examples are GreaterThan, LessThan, Equals.")] string segmentcomparison, + [Description("Value of the segment comparison. Some of the examples are 2023-08-01, India, Australia, Kenya. For country get 2 letter country code instead of country name.")] string segmentcomparisonvalue, + [Description("Name of the segment action. Examples are GrantVirtualCurrencyAction, EmailNotificationAction. This is empty if there is no segment action.")] string segmentaction, + [Description("Name of the segment action key or code. Examples are virtual currency code, email template id. This is empty if there is no segment action.")] string segmentactioncode, + [Description("Name of the segment action key value or code value. Examples are template name, virtual currency amount. This is empty if there is no segment action.")] string segmentactionvalue + ) + { + //ToDo: Create payload json using Playfab dlls/sdk + // Set properties to create a Segment using swagger.json + ContextVariables contextVariables = new(); + contextVariables.Set("content_type", "application/json"); + contextVariables.Set("server_url", TestConfiguration.PlayFab.Endpoint); + string segmentPayload = GetSegmentPayload(segmentname, segmentdefinition, segmentcomparison, segmentcomparisonvalue, segmentaction, segmentactioncode, segmentactionvalue); + + contextVariables.Set("content_type", "application/json"); + contextVariables.Set("payload", segmentPayload); + var kernel = new KernelBuilder().WithLogger(ConsoleLogger.Logger).Build(); + using HttpClient httpClient = new(); + var playfabApiSkills = await GetPlayFabSkill(kernel, httpClient); + + // Run operation via the semantic kernel + var result2 = await kernel.RunAsync(contextVariables, playfabApiSkills["CreateSegment"]); + + Console.WriteLine("\n\n\n"); + var formattedContent = JsonConvert.SerializeObject(JsonConvert.DeserializeObject(result2.Result), Formatting.Indented); + Console.WriteLine("CreateSegment playfabApiSkills response: \n{0}", formattedContent); + + return $"Segment {segmentname} created with segment definition {segmentdefinition}"; + } + + private static string GetSegmentPayload(string segmentname, string segmentdefinition, string segmentcomparison, string segmentcomparisonvalue, string segmentaction, string segmentactioncode, string segmentactionvalue) + { + //ToDo: Need to explore and replace this logic with open ai chat. + string segmentPayload = "{\n \"SegmentModel\": {\n \"Name\": \"\",\n \"SegmentOrDefinitions\": [\n {\n \"SegmentAndDefinitions\": [\n {\n \"\": {\n \"LogInDate\": \"T00:00:00Z\",\n \"Comparison\": \"\"\n }\n }\n ]\n }\n ]\n }\n }"; + string locationPayload = "{\n \"SegmentModel\": {\n \"Name\": \"\",\n \"SegmentOrDefinitions\": [\n {\n \"SegmentAndDefinitions\": [\n {\n \"\": {\n \"CountryCode\": \"\",\n \"Comparison\": \"\"\n }\n }\n ]\n }\n ]\n }\n }"; + + if (segmentdefinition == "LocationFilter") + { + segmentPayload = locationPayload; + } + + if (!string.IsNullOrEmpty(segmentaction) && segmentaction == "GrantVirtualCurrencyAction") + { + string enteredSegmentAction = $", \"EnteredSegmentActions\": [\n {{\n \"GrantVirtualCurrencyAction\": {{\n \"CurrencyCode\": \"{segmentactioncode}\",\n \"Amount\": {segmentactionvalue}\n }}\n }}\n ]"; + segmentPayload = segmentPayload.Replace("", enteredSegmentAction); + } + else + { + segmentPayload = segmentPayload.Replace("", string.Empty); + } + + segmentPayload = segmentPayload.Replace("", segmentname); + segmentPayload = segmentPayload.Replace("", segmentdefinition); + segmentPayload = segmentPayload.Replace("", segmentcomparison); + segmentPayload = segmentPayload.Replace("", segmentcomparisonvalue); + return segmentPayload; + } + + private static async Task> GetPlayFabSkill(IKernel kernel, HttpClient httpClient) + { + IDictionary playfabApiSkills; + + var titleSecretKeyProvider = new PlayFabAuthenticationProvider(() => + { + string s = TestConfiguration.PlayFab.TitleSecretKey; + return Task.FromResult(s); + }); + + bool useLocalFile = true; + if (useLocalFile) + { + var playfabApiFile = "../../../Skills/PlayFabApiSkill/openapi.json"; + playfabApiSkills = await kernel.ImportOpenApiSkillFromFileAsync("PlayFabApiSkill", playfabApiFile, new OpenApiSkillExecutionParameters(httpClient, authCallback: titleSecretKeyProvider.AuthenticateRequestAsync)); + } + else + { + var playfabApiRawFileUrl = new Uri(TestConfiguration.PlayFab.SwaggerEndpoint); + playfabApiSkills = await kernel.ImportOpenApiSkillFromUrlAsync("PlayFabApiSkill", playfabApiRawFileUrl, new OpenApiSkillExecutionParameters(httpClient, authCallback: titleSecretKeyProvider.AuthenticateRequestAsync)); + } + + return playfabApiSkills; + } +} diff --git a/dotnet/samples/PlayFabExamples/Example03_SegmentQuery/Example03_SegmentQuery.cs b/dotnet/samples/PlayFabExamples/Example03_SegmentQuery/Example03_SegmentQuery.cs new file mode 100644 index 000000000000..01510b2e3b52 --- /dev/null +++ b/dotnet/samples/PlayFabExamples/Example03_SegmentQuery/Example03_SegmentQuery.cs @@ -0,0 +1,75 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.Orchestration; +using Microsoft.SemanticKernel.SkillDefinition; +using Microsoft.SemanticKernel.Skills.OpenAPI.Authentication; +using Microsoft.SemanticKernel.Skills.OpenAPI.Extensions; +using Newtonsoft.Json; +using PlayFabExamples.Common.Configuration; +using PlayFabExamples.Common.Logging; + +namespace PlayFabExamples.Example03_SegmentQuery; + +/// +/// This example shows how to import PlayFab APIs as skills. +/// +// ReSharper disable once InconsistentNaming +public static class Example03_SegmentQuery +{ + public static async Task RunAsync() + { + await SkillImportExample(); + } + + private static async Task SkillImportExample() + { + var kernel = new KernelBuilder().WithLogger(ConsoleLogger.Logger).Build(); + var contextVariables = new ContextVariables(); + + contextVariables.Set("server_url", TestConfiguration.PlayFab.Endpoint); + + using HttpClient httpClient = new(); + + var playfabApiSkills = await GetPlayFabSkill(kernel, httpClient); + + // GetSegments skill + { + // Set properties for the Get Segments operation in the openAPI.swagger.json + contextVariables.Set("content_type", "application/json"); + contextVariables.Set("payload", "{ \"SegmentIds\": [] }"); + + // Run operation via the semantic kernel + var result = await kernel.RunAsync(contextVariables, playfabApiSkills["GetSegments"]); + + Console.WriteLine("\n\n\n"); + var formattedContent = JsonConvert.SerializeObject(JsonConvert.DeserializeObject(result.Result), Formatting.Indented); + Console.WriteLine("GetSegments playfabApiSkills response: \n{0}", formattedContent); + } + } + + private static async Task> GetPlayFabSkill(IKernel kernel, HttpClient httpClient) + { + IDictionary playfabApiSkills; + + var titleSecretKeyProvider = new PlayFabAuthenticationProvider(() => + { + string s = TestConfiguration.PlayFab.TitleSecretKey; + return Task.FromResult(s); + }); + + bool useLocalFile = true; + if (useLocalFile) + { + var playfabApiFile = "../../../Skills/PlayFabApiSkill/openapi.json"; + playfabApiSkills = await kernel.ImportOpenApiSkillFromFileAsync("PlayFabApiSkill", playfabApiFile, new OpenApiSkillExecutionParameters(httpClient, authCallback: titleSecretKeyProvider.AuthenticateRequestAsync)); ; + } + else + { + var playfabApiRawFileUrl = new Uri(TestConfiguration.PlayFab.SwaggerEndpoint); + playfabApiSkills = await kernel.ImportOpenApiSkillFromUrlAsync("PlayFabApiSkill", playfabApiRawFileUrl, new OpenApiSkillExecutionParameters(httpClient, authCallback: titleSecretKeyProvider.AuthenticateRequestAsync)); ; + } + + return playfabApiSkills; + } +} diff --git a/dotnet/samples/PlayFabExamples/PlayFabExamples.csproj b/dotnet/samples/PlayFabExamples/PlayFabExamples.csproj new file mode 100644 index 000000000000..832c84ed7de9 --- /dev/null +++ b/dotnet/samples/PlayFabExamples/PlayFabExamples.csproj @@ -0,0 +1,37 @@ + + + + Exe + net7.0 + 11 + enable + enable + 4447f5a3-3bc4-4697-a289-b87c718828fc + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dotnet/samples/PlayFabExamples/Program.cs b/dotnet/samples/PlayFabExamples/Program.cs new file mode 100644 index 000000000000..01ce283c24b2 --- /dev/null +++ b/dotnet/samples/PlayFabExamples/Program.cs @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Microsoft.Extensions.Configuration; +using PlayFabExamples.Common.Configuration; + +namespace PlayFabExamples; + +public static class Program +{ + public static async Task Main(string[] args) + { + // Load configuration from environment variables or user secrets. + LoadUserSecrets(); + + // Execution canceled if the user presses Ctrl+C. + using CancellationTokenSource cancellationTokenSource = new(); + CancellationToken cancelToken = cancellationTokenSource.ConsoleCancellationToken(); + + // Run PlayFab Examples + await PlayFabExamples.Example01_DataQnA.Example01_DataQnA.RunAsync().SafeWaitAsync(cancelToken); + await PlayFabExamples.Example02_Generative.Example02_Generative.RunAsync().SafeWaitAsync(cancelToken); + await PlayFabExamples.Example03_SegmentQuery.Example03_SegmentQuery.RunAsync().SafeWaitAsync(cancelToken); + } + + private static void LoadUserSecrets() + { + IConfigurationRoot configRoot = new ConfigurationBuilder() + .AddEnvironmentVariables() + .AddUserSecrets() + .Build(); + TestConfiguration.Initialize(configRoot); + } + + private static CancellationToken ConsoleCancellationToken(this CancellationTokenSource tokenSource) + { + Console.CancelKeyPress += (s, e) => + { + Console.WriteLine("Canceling..."); + tokenSource.Cancel(); + e.Cancel = true; + }; + + return tokenSource.Token; + } + + private static async Task SafeWaitAsync(this Task task, + CancellationToken cancellationToken = default) + { + try + { + await task.WaitAsync(cancellationToken); + Console.WriteLine("== DONE =="); + } + catch (ConfigurationNotFoundException ex) + { + Console.WriteLine($"{ex.Message}. Skipping example."); + } + + cancellationToken.ThrowIfCancellationRequested(); + } +} diff --git a/dotnet/src/Extensions/Planning.StepwisePlanner/StepwisePlanner.cs b/dotnet/src/Extensions/Planning.StepwisePlanner/StepwisePlanner.cs index 4557d1a071c9..a6ac4b14213f 100644 --- a/dotnet/src/Extensions/Planning.StepwisePlanner/StepwisePlanner.cs +++ b/dotnet/src/Extensions/Planning.StepwisePlanner/StepwisePlanner.cs @@ -151,7 +151,7 @@ public Plan CreatePlan(string goal) await Task.Delay(this.Config.MinIterationTimeMs).ConfigureAwait(false); var result = await this.InvokeActionAsync(nextStep.Action!, nextStep!.ActionVariables!).ConfigureAwait(false); - if (string.IsNullOrEmpty(result)) + if (string.IsNullOrEmpty(result) || result.StartsWith("Error occurred")) { nextStep.Observation = "Got no result from action"; } diff --git a/dotnet/src/PF-Extensions/Planning.ChatStepwisePlanner/ChatStepwisePlanner.cs b/dotnet/src/PF-Extensions/Planning.ChatStepwisePlanner/ChatStepwisePlanner.cs new file mode 100644 index 000000000000..8386f3d1166d --- /dev/null +++ b/dotnet/src/PF-Extensions/Planning.ChatStepwisePlanner/ChatStepwisePlanner.cs @@ -0,0 +1,542 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Globalization; +using System.Linq; +using System.Text.Json; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.SemanticKernel.AI.ChatCompletion; +using Microsoft.SemanticKernel.Diagnostics; +using Microsoft.SemanticKernel.Orchestration; +using Microsoft.SemanticKernel.Planning.ChatStepwise; +using Microsoft.SemanticKernel.Planning.Stepwise; +using Microsoft.SemanticKernel.SemanticFunctions; +using Microsoft.SemanticKernel.SkillDefinition; + +#pragma warning disable IDE0130 +// ReSharper disable once CheckNamespace - Using NS of Plan +namespace Microsoft.SemanticKernel.Planning; + +/// +/// A planner that creates a Stepwise plan using Mrkl systems and leveraging the chat interface of LLM Models. +/// +/// +/// An implementation of a Mrkl system as described in https://arxiv.org/pdf/2205.00445.pdf +/// +public class ChatStepwisePlanner +{ + /// + /// Initialize a new instance of the class. + /// + /// The semantic kernel instance. + /// Optional configuration object + /// Optional prompt override + public ChatStepwisePlanner( + IKernel kernel, + StepwisePlannerConfig? config = null, + string? systemPrompt = null, + FewShotExample[]? fewShotExamples = null) + { + this._chatCompletion = kernel.GetService(); // Throws if not avaialble. + + this._kernel = kernel ?? throw new ArgumentNullException(nameof(kernel)); + + this.Config = config ?? new(); + this.Config.ExcludedSkills.Add(RestrictedSkillName); + + var promptConfig = new PromptTemplateConfig(); + var systemPromptTemplate = systemPrompt ?? EmbeddedResource.Read("Microsoft.SemanticKernel.Planning.ChatStepwise.Prompts.systemprompt.txt"); + var fewShotExamplesTemplate = fewShotExamples ?? JsonSerializer.Deserialize(EmbeddedResource.Read("Microsoft.SemanticKernel.Planning.ChatStepwise.Prompts.fewshotexamples.json")); + string promptConfigString = EmbeddedResource.Read("Microsoft.SemanticKernel.Planning.ChatStepwise.Prompts.config.json"); + if (!string.IsNullOrEmpty(promptConfigString)) + { + promptConfig = PromptTemplateConfig.FromJson(promptConfigString); + } + + promptConfig.Completion.MaxTokens = this.Config.MaxTokens; + + //this._systemStepFunction = this.ImportSemanticFunction(this._kernel, "StepwiseStep", promptTemplate, promptConfig); + this._nativeFunctions = this._kernel.ImportSkill(this, RestrictedSkillName); + + this._context = this._kernel.CreateNewContext(); + this._logger = this._kernel.Log; + } + + public Plan CreatePlan(string goal) + { + if (string.IsNullOrEmpty(goal)) + { + throw new PlanningException(PlanningException.ErrorCodes.InvalidGoal, "The goal specified is empty"); + } + + string functionDescriptions = this.GetFunctionDescriptions(); + + Plan planStep = new(this._nativeFunctions["ExecutePlan"]); + planStep.Parameters.Set("functionDescriptions", functionDescriptions); + planStep.Parameters.Set("question", goal); + + //planStep.Outputs.Add("agentScratchPad"); + planStep.Outputs.Add("stepCount"); + planStep.Outputs.Add("skillCount"); + planStep.Outputs.Add("stepsTaken"); + + Plan plan = new(goal); + + plan.AddSteps(planStep); + + return plan; + } + + [SKFunction, SKName("ExecutePlan"), Description("Execute a plan")] + public async Task ExecutePlanAsync( + [Description("The question to answer")] + string question, + [Description("List of tool descriptions")] + string functionDescriptions, + SKContext context) + { + var stepsTaken = new List(); + this._logger?.BeginScope("ChatStepwisePlanner"); + if (!string.IsNullOrEmpty(question)) + { + this._logger?.LogInformation("Ask: {Question}", question); + + // TODO MOVE + + var systemPromptTemplate = EmbeddedResource.Read("Microsoft.SemanticKernel.Planning.ChatStepwise.Prompts.systemprompt.txt"); + var fewShotExamplesTemplate = JsonSerializer.Deserialize(EmbeddedResource.Read("Microsoft.SemanticKernel.Planning.ChatStepwise.Prompts.fewshotexamples.json")); + + // TODO use semantic function to fill in function descriptions + string currentTime = DateTimeOffset.UtcNow.ToString("f", DateTimeFormatInfo.InvariantInfo); + ChatHistory history = this._chatCompletion.CreateNewChat(systemPromptTemplate.Replace("{{$functionDescriptions}}", functionDescriptions).Replace("{{time.UtcNow}}", currentTime)); + foreach (FewShotExample example in fewShotExamplesTemplate) + { + switch (example.Role) + { + case Role.User: + history.AddUserMessage(example.Content); + break; + + case Role.Assistant: + history.AddAssistantMessage(example.Content); + break; + + default: + throw new ArgumentException("Invalid role specified."); + } + } + + history.AddUserMessage(question); + + for (int i = 0; i < this.Config.MaxIterations; i++) + { + //var scratchPad = this.CreateScratchPad(question, stepsTaken); + //this._logger?.LogDebug("Scratchpad: {ScratchPad}", scratchPad); + //context.Variables.Set("agentScratchPad", scratchPad); + + // TODO request settings and cancellation token + var llmResponse = (await this._chatCompletion.GetChatCompletionsAsync(history).ConfigureAwait(false)); + var llmResult = llmResponse.Single(); // TODO, is there a case where we get multiple messages? + var responseMessage = await llmResult.GetChatMessageAsync().ConfigureAwait(false); + + /*if (response.ErrorOccurred) + { + var exception = new PlanningException(PlanningException.ErrorCodes.UnknownError, $"Error occurred while executing stepwise plan: {llmResponse.LastErrorDescription}", llmResponse.LastException); + context.Fail(exception.Message, exception); + return context; + }*/ + + string actionText = responseMessage.Content.Trim(); + history.AddAssistantMessage(actionText); + this._logger?.LogDebug("Response : {ActionText}", actionText); + + var nextStep = this.ParseResult(actionText); + stepsTaken.Add(nextStep); + + if (!string.IsNullOrEmpty(nextStep.FinalAnswer)) + { + this._logger?.LogInformation("Final Answer: {FinalAnswer}", nextStep.FinalAnswer); + context.Variables.Update(nextStep.FinalAnswer); + var updatedScratchPlan = this.CreateScratchPad(question, stepsTaken); + context.Variables.Set("agentScratchPad", updatedScratchPlan); + + // Add additional results to the context + this.AddExecutionStatsToContext(stepsTaken, context); + + return context; + } + + this._logger?.LogInformation("Thought: {Thought}", nextStep.Thought); + + if (!string.IsNullOrEmpty(nextStep!.Action!)) + { + this._logger?.LogInformation("Action: {Action}({ActionVariables})", nextStep.Action, JsonSerializer.Serialize(nextStep.ActionVariables)); + try + { + await Task.Delay(this.Config.MinIterationTimeMs).ConfigureAwait(false); + var result = await this.InvokeActionAsync(nextStep.Action!, nextStep!.ActionVariables!).ConfigureAwait(false); + + if (string.IsNullOrEmpty(result)) + { + nextStep.Observation = "Got no result from action"; + } + else + { + nextStep.Observation = result; + } + } + catch (Exception ex) when (!ex.IsCriticalException()) + { + nextStep.Observation = $"Error invoking action {nextStep.Action} : {ex.Message}"; + this._logger?.LogDebug(ex, "Error invoking action {Action}", nextStep.Action); + } + + this._logger?.LogInformation("Observation: {Observation}", nextStep.Observation); + history.AddAssistantMessage("[OBSERVATION] " + nextStep.Observation); + } + else + { + this._logger?.LogInformation("Action: No action to take"); + } + + // sleep 3 seconds + await Task.Delay(this.Config.MinIterationTimeMs).ConfigureAwait(false); + } + + context.Variables.Update($"Result not found, review _stepsTaken to see what happened.\n{JsonSerializer.Serialize(stepsTaken)}"); + } + else + { + context.Variables.Update("Question not found."); + } + + return context; + } + + public virtual SystemStep ParseResult(string input) + { + var result = new SystemStep + { + OriginalResponse = input + }; + + // Extract final answer + Match finalAnswerMatch = s_finalAnswerRegex.Match(input); + + if (finalAnswerMatch.Success) + { + result.FinalAnswer = finalAnswerMatch.Groups[1].Value.Trim(); + return result; + } + + // Extract thought + Match thoughtMatch = s_thoughtRegex.Match(input); + + if (thoughtMatch.Success) + { + result.Thought = thoughtMatch.Value.Trim(); + } + else if (!input.Contains(Action)) + { + result.Thought = input; + } + else + { + throw new InvalidOperationException("Unexpected input format"); + } + + result.Thought = result.Thought.Replace(Thought, string.Empty).Trim(); + + // Extract action + Match actionMatch = s_actionRegex.Match(input); + + if (actionMatch.Success) + { + var json = actionMatch.Groups[1].Value.Trim(); + + try + { + var systemStepResults = JsonSerializer.Deserialize(json); + + if (systemStepResults == null) + { + result.Observation = $"System step parsing error, empty JSON: {json}"; + } + else + { + result.Action = systemStepResults.Action; + result.ActionVariables = systemStepResults.ActionVariables; + } + } + catch (JsonException) + { + result.Observation = $"System step parsing error, invalid JSON: {json}"; + } + } + + if (string.IsNullOrEmpty(result.Thought) && string.IsNullOrEmpty(result.Action)) + { + result.Observation = "System step error, no thought or action found. Please give a valid thought and/or action."; + } + + return result; + } + + private void AddExecutionStatsToContext(List stepsTaken, SKContext context) + { + context.Variables.Set("stepCount", stepsTaken.Count.ToString(CultureInfo.InvariantCulture)); + context.Variables.Set("stepsTaken", JsonSerializer.Serialize(stepsTaken)); + + Dictionary actionCounts = new(); + foreach (var step in stepsTaken) + { + if (string.IsNullOrEmpty(step.Action)) { continue; } + + _ = actionCounts.TryGetValue(step.Action!, out int currentCount); + actionCounts[step.Action!] = ++currentCount; + } + + var skillCallListWithCounts = string.Join(", ", actionCounts.Keys.Select(skill => + $"{skill}({actionCounts[skill]})")); + + var skillCallCountStr = actionCounts.Values.Sum().ToString(CultureInfo.InvariantCulture); + + context.Variables.Set("skillCount", $"{skillCallCountStr} ({skillCallListWithCounts})"); + } + + private string CreateScratchPad(string question, List stepsTaken) + { + if (stepsTaken.Count == 0) + { + return string.Empty; + } + + var scratchPadLines = new List(); + + // Add the original first thought + scratchPadLines.Add(ScratchPadPrefix); + scratchPadLines.Add($"{Thought} {stepsTaken[0].Thought}"); + + // Keep track of where to insert the next step + var insertPoint = scratchPadLines.Count; + + // Keep the most recent steps in the scratch pad. + for (var i = stepsTaken.Count - 1; i >= 0; i--) + { + if (scratchPadLines.Count / 4.0 > (this.Config.MaxTokens * 0.75)) + { + this._logger.LogDebug("Scratchpad is too long, truncating. Skipping {CountSkipped} steps.", i + 1); + break; + } + + var s = stepsTaken[i]; + + if (!string.IsNullOrEmpty(s.Observation)) + { + scratchPadLines.Insert(insertPoint, $"{Observation} {s.Observation}"); + } + + if (!string.IsNullOrEmpty(s.Action)) + { + scratchPadLines.Insert(insertPoint, $"{Action} {{\"action\": \"{s.Action}\",\"action_variables\": {JsonSerializer.Serialize(s.ActionVariables)}}}"); + } + + if (i != 0) + { + scratchPadLines.Insert(insertPoint, $"{Thought} {s.Thought}"); + } + } + + return string.Join("\n", scratchPadLines).Trim(); + } + + private async Task InvokeActionAsync(string actionName, Dictionary actionVariables) + { + var availableFunctions = this.GetAvailableFunctions(); + var targetFunction = availableFunctions.FirstOrDefault(f => ToFullyQualifiedName(f) == actionName); + if (targetFunction == null) + { + throw new PlanningException(PlanningException.ErrorCodes.UnknownError, $"The function '{actionName}' was not found."); + } + + try + { + var function = this._kernel.Func(targetFunction.SkillName, targetFunction.Name); + var actionContext = this.CreateActionContext(actionVariables); + + var result = await function.InvokeAsync(actionContext).ConfigureAwait(false); + + if (result.ErrorOccurred) + { + this._logger?.LogError("Error occurred: {Error}", result.LastException); + return $"Error occurred: {result.LastException}"; + } + + this._logger?.LogDebug("Invoked {FunctionName}. Result: {Result}", targetFunction.Name, result.Result); + + return result.Result; + } + catch (Exception e) when (!e.IsCriticalException()) + { + this._logger?.LogError(e, "Something went wrong in system step: {0}.{1}. Error: {2}", targetFunction.SkillName, targetFunction.Name, e.Message); + return $"Something went wrong in system step: {targetFunction.SkillName}.{targetFunction.Name}. Error: {e.Message} {e.InnerException.Message}"; + } + } + + private SKContext CreateActionContext(Dictionary actionVariables) + { + var actionContext = this._kernel.CreateNewContext(); + if (actionVariables != null) + { + foreach (var kvp in actionVariables) + { + actionContext.Variables.Set(kvp.Key, kvp.Value); + } + } + + return actionContext; + } + + private IEnumerable GetAvailableFunctions() + { + FunctionsView functionsView = this._context.Skills!.GetFunctionsView(); + + var excludedSkills = this.Config.ExcludedSkills ?? new(); + var excludedFunctions = this.Config.ExcludedFunctions ?? new(); + + var availableFunctions = + functionsView.NativeFunctions + .Concat(functionsView.SemanticFunctions) + .SelectMany(x => x.Value) + .Where(s => !excludedSkills.Contains(s.SkillName) && !excludedFunctions.Contains(s.Name)) + .OrderBy(x => x.SkillName) + .ThenBy(x => x.Name); + return availableFunctions; + } + + private string GetFunctionDescriptions() + { + var availableFunctions = this.GetAvailableFunctions(); + + string functionDescriptions = string.Join("\n", availableFunctions.Select(x => ToManualString(x))); + return functionDescriptions; + } + + private ISKFunction ImportSemanticFunction(IKernel kernel, string functionName, string promptTemplate, PromptTemplateConfig config) + { + var template = new PromptTemplate(promptTemplate, config, kernel.PromptTemplateEngine); + var functionConfig = new SemanticFunctionConfig(config, template); + + return kernel.RegisterSemanticFunction(RestrictedSkillName, functionName, functionConfig); + } + + private static string ToManualString(FunctionView function) + { + var inputs = string.Join("\n", function.Parameters.Select(parameter => + { + var defaultValueString = string.IsNullOrEmpty(parameter.DefaultValue) ? string.Empty : $"(default='{parameter.DefaultValue}')"; + return $" - {parameter.Name}: {parameter.Description} {defaultValueString}"; + })); + + var functionDescription = function.Description.Trim(); + + if (string.IsNullOrEmpty(inputs)) + { + return $"{ToFullyQualifiedName(function)}: {functionDescription}\n"; + } + + return $"{ToFullyQualifiedName(function)}: {functionDescription}\n{inputs}\n"; + } + + private static string ToFullyQualifiedName(FunctionView function) + { + return $"{function.SkillName}.{function.Name}"; + } + + /// + /// The configuration for the StepwisePlanner + /// + private StepwisePlannerConfig Config { get; } + + // Context used to access the list of functions in the kernel + private readonly SKContext _context; + private readonly IKernel _kernel; + private readonly ILogger _logger; + private readonly IChatCompletion _chatCompletion; + + /// + /// Planner native functions + /// + private IDictionary _nativeFunctions = new Dictionary(); + + /// + /// System step function for Plan execution + /// + private ISKFunction _systemStepFunction; + + /// + /// The name to use when creating semantic functions that are restricted from plan creation + /// + private const string RestrictedSkillName = "StepwisePlanner_Excluded"; + + /// + /// The Action tag + /// + private const string Action = "[ACTION]"; + + /// + /// The Thought tag + /// + private const string Thought = "[THOUGHT]"; + + /// + /// The Observation tag + /// + private const string Observation = "[OBSERVATION]"; + + /// + /// The prefix used for the scratch pad + /// + private const string ScratchPadPrefix = "This was my previous work (but they haven't seen any of it! They only see what I return as final answer):"; + + /// + /// The regex for parsing the action response + /// + private static readonly Regex s_actionRegex = new(@"\[ACTION\][^{}]*({(?:[^{}]*{[^{}]*})*[^{}]*})", RegexOptions.Singleline); + + /// + /// The regex for parsing the thought response + /// + private static readonly Regex s_thoughtRegex = new(@"(\[THOUGHT\])?(?.+?)(?=\[ACTION\]|$)", RegexOptions.Singleline); + + /// + /// The regex for parsing the final answer response + /// + private static readonly Regex s_finalAnswerRegex = new(@"\[FINAL ANSWER\](?.+)", RegexOptions.Singleline); +} + +/// +/// Exception extension methods. +/// +internal static class ExceptionExtensions +{ + /// + /// Check if an exception is of a type that should not be caught by the kernel. + /// + /// Exception. + /// True if is a critical exception and should not be caught. + internal static bool IsCriticalException(this Exception ex) + => ex is OutOfMemoryException + or ThreadAbortException + or AccessViolationException + or AppDomainUnloadedException + or BadImageFormatException + or CannotUnloadAppDomainException + or InvalidProgramException + or StackOverflowException; +} diff --git a/dotnet/src/PF-Extensions/Planning.ChatStepwisePlanner/EmbeddedResource.cs b/dotnet/src/PF-Extensions/Planning.ChatStepwisePlanner/EmbeddedResource.cs new file mode 100644 index 000000000000..783977191c09 --- /dev/null +++ b/dotnet/src/PF-Extensions/Planning.ChatStepwisePlanner/EmbeddedResource.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.IO; +using System.Reflection; + +#pragma warning disable IDE0130 +// ReSharper disable once CheckNamespace - Using NS of Plan +namespace Microsoft.SemanticKernel.Planning.ChatStepwise; + +public static class EmbeddedResource +{ + internal static string Read(string resourceName) + { + Assembly assembly = Assembly.GetExecutingAssembly(); + + using Stream stream = assembly.GetManifestResourceStream(resourceName); + using StreamReader reader = new StreamReader(stream); + return reader.ReadToEnd(); + } +} diff --git a/dotnet/src/PF-Extensions/Planning.ChatStepwisePlanner/FewShotExample.cs b/dotnet/src/PF-Extensions/Planning.ChatStepwisePlanner/FewShotExample.cs new file mode 100644 index 000000000000..34a0d8a901a3 --- /dev/null +++ b/dotnet/src/PF-Extensions/Planning.ChatStepwisePlanner/FewShotExample.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Text.Json.Serialization; + +#pragma warning disable IDE0130 +// ReSharper disable once CheckNamespace - Using NS of Plan +namespace Microsoft.SemanticKernel.Planning; + +public class FewShotExample +{ + [JsonConverter(typeof(JsonStringEnumConverter))] + [JsonPropertyName("role")] + public Role Role { get; set; } + + [JsonPropertyName("content")] + public string? Content { get; set; } +} diff --git a/dotnet/src/PF-Extensions/Planning.ChatStepwisePlanner/Planning.ChatStepwisePlanner.csproj b/dotnet/src/PF-Extensions/Planning.ChatStepwisePlanner/Planning.ChatStepwisePlanner.csproj new file mode 100644 index 000000000000..f1192c47a58e --- /dev/null +++ b/dotnet/src/PF-Extensions/Planning.ChatStepwisePlanner/Planning.ChatStepwisePlanner.csproj @@ -0,0 +1,37 @@ + + + + + Microsoft.SemanticKernel.Planning.ChatStepwisePlanner + Microsoft.SemanticKernel.Planning.ChatStepwise + netstandard2.0 + + + + + + + + Semantic Kernel - Stepwise Planner + Semantic Kernel Stepwise Planner + + + + + + + + + + + + + + + + + + + + + diff --git a/dotnet/src/PF-Extensions/Planning.ChatStepwisePlanner/Prompts/config.json b/dotnet/src/PF-Extensions/Planning.ChatStepwisePlanner/Prompts/config.json new file mode 100644 index 000000000000..51ef104e4597 --- /dev/null +++ b/dotnet/src/PF-Extensions/Planning.ChatStepwisePlanner/Prompts/config.json @@ -0,0 +1,32 @@ +{ + "schema": 1, + "description": "Given a request or command or goal generate multi-step plan to reach the goal. After each step LLM is called to perform the reasoning for the next step.", + "type": "completion", + "completion": { + "max_tokens": 1024, + "temperature": 0, + "top_p": 0, + "presence_penalty": 0, + "frequency_penalty": 0, + "stop_sequences": ["[OBSERVATION]", "\n[THOUGHT]"] + }, + "input": { + "parameters": [ + { + "name": "question", + "description": "The question to answer", + "defaultValue": "" + }, + { + "name": "agentScratchPad", + "description": "The agent's scratch pad", + "defaultValue": "" + }, + { + "name": "functionDescriptions", + "description": "The manual of the agent's functions", + "defaultValue": "" + } + ] + } +} diff --git a/dotnet/src/PF-Extensions/Planning.ChatStepwisePlanner/Prompts/fewshotexamples.json b/dotnet/src/PF-Extensions/Planning.ChatStepwisePlanner/Prompts/fewshotexamples.json new file mode 100644 index 000000000000..0028ad5f7ec7 --- /dev/null +++ b/dotnet/src/PF-Extensions/Planning.ChatStepwisePlanner/Prompts/fewshotexamples.json @@ -0,0 +1,6 @@ +[ + {"role": "user", "content": "What is the size of an Olympic Swimming Pool?"}, + {"role": "assistant", "content": "[THOUGHT] The volume of an Olympic-size swimming pool is not readily available in the observation. We need to perform a web search to find this information.\n[ACTION] {\"action\": \"WebSearch.Search\",\"action_variables\": {\"query\":\"size of Olympic swimming pool\",\"searchQuestion\":\"What is the size of an size of an Olympic swimming pool?\"}"}, + {"role": "assistant", "content": "[OBSERVATION] An Olympic-size swimming pool conforms to regulated dimensions that are big enough for international competition. This type of swimming pool is used in the Olympic Games, where the race course is 50 metres (164.0 ft) in length, typically referred to as \"long course\", distinguishing it from \"short course\" which applies to competitions in pools that are 25 metres (82.0 ft) in length, or 25 yards (22.86 m) in the United States. If touch panels are used in competition, then the distance between touch panels should be either 25 or 50 metres to qualify for FINA recognition. This means that Olympic pools are generally oversized, to accommodate touch panels used in competition."}, + {"role": "assistant", "content": "[FINAL ANSWER] An Olympic-sized swimming pool conforms to regulated dimensions that are large enough for international competition. The race course is 50 meters (164.0 ft) in length and 25 meters in width. The depth of an Olympic swimming pool is 2 meters (6 ft 7 in) minimum, 3 meters (9 ft 10 in) recommended. An Olympic swimming pool holds approximately 2.5 million liters of water with a surface area of approximately 13,000 square feet."} +] diff --git a/dotnet/src/PF-Extensions/Planning.ChatStepwisePlanner/Prompts/systemprompt.txt b/dotnet/src/PF-Extensions/Planning.ChatStepwisePlanner/Prompts/systemprompt.txt new file mode 100644 index 000000000000..64e18b35249d --- /dev/null +++ b/dotnet/src/PF-Extensions/Planning.ChatStepwisePlanner/Prompts/systemprompt.txt @@ -0,0 +1,46 @@ +* Answer the user as accurately as possible using the provided functions. +* Break down the problem step by step and think about the best approach +* Generate a single [THOUGHT] and an optional single [ACTION]. The resulting output of an [ACTION] will be provided as an [OBSERVATION] +* Always use the functions provided in the [AVAILABLE FUNCTIONS] section when possible regardless of your internal knowledge or information. +* Once I have gathered all the necessary observations and performed any required actions, I can provide the [FINAL ANSWER] in a clear and human-readable format. + + +[AVAILABLE FUNCTIONS] +The function definitions below are in the following format: +: + - : + - ... + +{{$functionDescriptions}} +[END AVAILABLE FUNCTIONS] + +[USAGE INSTRUCTIONS] +To use the functions, specify a JSON blob representing an action. The JSON blob should contain an "action" key with the name of the function to use, and an "action_variables" key with a JSON object of string values to use when calling the function. +Do not call functions directly; they must be invoked through an action. +The "action_variables" value should always include an "input" key, even if the input value is empty. Additional keys in the "action_variables" value should match the defined [PARAMETERS] of the named "action" in [AVAILABLE FUNCTIONS]. +Dictionary values in "action_variables" must be strings and represent the actual values to be passed to the function. +Ensure that the $JSON_BLOB contains only a SINGLE action; do NOT return multiple actions. +IMPORTANT: Use only the available functions listed in the [AVAILABLE FUNCTIONS] section. Do not attempt to use any other functions that are not specified. + +Here is an example of a valid $JSON_BLOB: +{ + "action": "FUNCTION.NAME", + "action_variables": {"INPUT": "some input", "PARAMETER_NAME": "some value", "PARAMETER_NAME_2": "42"} +} +[END USAGE INSTRUCTIONS] + +[THOUGHT PROCESS] +[QUESTION] +the input question I must answer +[THOUGHT] +To solve this problem, I should carefully analyze the given question and identify the necessary steps. Any facts I discover earlier in my thought process should be repeated here to keep them readily available. +[ACTION] +$JSON_BLOB +[OBSERVATION] +The result of the action will be provided here. +... (These Thought/Action/Observation can repeat until the final answer is reached.) +[FINAL ANSWER] +Once I have gathered all the necessary observations and performed any required actions, I can provide the final answer in a clear and human-readable format. +[END THOUGHT PROCESS] + +At the beginning of this chat exchange, the time is {{time.UtcNow}} diff --git a/dotnet/src/PF-Extensions/Planning.ChatStepwisePlanner/Role.cs b/dotnet/src/PF-Extensions/Planning.ChatStepwisePlanner/Role.cs new file mode 100644 index 000000000000..3063589e8e18 --- /dev/null +++ b/dotnet/src/PF-Extensions/Planning.ChatStepwisePlanner/Role.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Text.Json.Serialization; + +#pragma warning disable IDE0130 +// ReSharper disable once CheckNamespace - Using NS of Plan +namespace Microsoft.SemanticKernel.Planning; +#pragma warning restore IDE0130 + +// TODO fix with serialization options not attributes +public enum Role +{ + [JsonPropertyName("user")] + User, + [JsonPropertyName("assistant")] + Assistant +} diff --git a/dotnet/src/Skills/Skills.OpenAPI/Authentication/PlayFabAuthenticationProvider.cs b/dotnet/src/Skills/Skills.OpenAPI/Authentication/PlayFabAuthenticationProvider.cs new file mode 100644 index 000000000000..9a5a8b8357c3 --- /dev/null +++ b/dotnet/src/Skills/Skills.OpenAPI/Authentication/PlayFabAuthenticationProvider.cs @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading.Tasks; + +namespace Microsoft.SemanticKernel.Skills.OpenAPI.Authentication; + +/// +/// Retrieves the title secret key via the provided delegate and +/// applies it to HTTP requests using the required authentication scheme. +/// +public class PlayFabAuthenticationProvider +{ + private readonly Func> _titleSecretKey; + + /// + /// Creates an instance of the class. + /// + /// Delegate for retrieving the title secret key. + public PlayFabAuthenticationProvider(Func> titleSecretKey) + { + this._titleSecretKey = titleSecretKey; + } + + /// + /// Applies the authentication content to the provided HTTP request message. + /// + /// The HTTP request message. + public async Task AuthenticateRequestAsync(HttpRequestMessage request) + { + var titleSecretKey = await this._titleSecretKey().ConfigureAwait(false); + request.Headers.Add("X-SecretKey", titleSecretKey); + } +}