77using DevProxy . Abstractions . Plugins ;
88using DevProxy . Abstractions . Proxy ;
99using DevProxy . Abstractions . Utils ;
10+ using DevProxy . Plugins . Utils ;
1011using Microsoft . Extensions . Configuration ;
1112using Microsoft . Extensions . DependencyInjection ;
1213using Microsoft . Extensions . Logging ;
1920using System . Diagnostics ;
2021using System . Diagnostics . Metrics ;
2122using System . Text . Json ;
22- using Titanium . Web . Proxy . Http ;
2323
2424namespace DevProxy . Plugins . Inspection ;
2525
@@ -108,7 +108,7 @@ public override Task BeforeRequestAsync(ProxyRequestArgs e, CancellationToken ca
108108 return Task . CompletedTask ;
109109 }
110110
111- if ( ! TryGetOpenAIRequest ( request . BodyString , out var openAiRequest ) || openAiRequest is null )
111+ if ( ! OpenAIRequest . TryGetOpenAIRequest ( request . BodyString , Logger , out var openAiRequest ) || openAiRequest is null )
112112 {
113113 Logger . LogRequest ( "Skipping non-OpenAI request" , MessageType . Skipped , new ( e . Session ) ) ;
114114 return Task . CompletedTask ;
@@ -323,9 +323,9 @@ private void ProcessSuccessResponse(Activity activity, ProxyResponseArgs e)
323323 }
324324
325325 var bodyString = response . BodyString ;
326- if ( IsStreamingResponse ( response ) )
326+ if ( HttpUtils . IsStreamingResponse ( response , Logger ) )
327327 {
328- bodyString = GetBodyFromStreamingResponse ( response ) ;
328+ bodyString = HttpUtils . GetBodyFromStreamingResponse ( response , Logger ) ;
329329 }
330330
331331 AddResponseTypeSpecificTags ( activity , openAiRequest , bodyString ) ;
@@ -895,95 +895,6 @@ private void RecordUsageMetrics(Activity activity, OpenAIRequest request, OpenAI
895895 Logger . LogTrace ( "RecordUsageMetrics() finished" ) ;
896896 }
897897
898- private bool TryGetOpenAIRequest ( string content , out OpenAIRequest ? request )
899- {
900- Logger . LogTrace ( "TryGetOpenAIRequest() called" ) ;
901-
902- request = null ;
903-
904- if ( string . IsNullOrEmpty ( content ) )
905- {
906- Logger . LogDebug ( "Request content is empty or null" ) ;
907- return false ;
908- }
909-
910- try
911- {
912- Logger . LogDebug ( "Checking if the request is an OpenAI request..." ) ;
913-
914- var rawRequest = JsonSerializer . Deserialize < JsonElement > ( content , ProxyUtils . JsonSerializerOptions ) ;
915-
916- // Check for completion request (has "prompt", but not specific to image)
917- if ( rawRequest . TryGetProperty ( "prompt" , out _ ) &&
918- ! rawRequest . TryGetProperty ( "size" , out _ ) &&
919- ! rawRequest . TryGetProperty ( "n" , out _ ) )
920- {
921- Logger . LogDebug ( "Request is a completion request" ) ;
922- request = JsonSerializer . Deserialize < OpenAICompletionRequest > ( content , ProxyUtils . JsonSerializerOptions ) ;
923- return true ;
924- }
925-
926- // Chat completion request
927- if ( rawRequest . TryGetProperty ( "messages" , out _ ) )
928- {
929- Logger . LogDebug ( "Request is a chat completion request" ) ;
930- request = JsonSerializer . Deserialize < OpenAIChatCompletionRequest > ( content , ProxyUtils . JsonSerializerOptions ) ;
931- return true ;
932- }
933-
934- // Embedding request
935- if ( rawRequest . TryGetProperty ( "input" , out _ ) &&
936- rawRequest . TryGetProperty ( "model" , out _ ) &&
937- ! rawRequest . TryGetProperty ( "voice" , out _ ) )
938- {
939- Logger . LogDebug ( "Request is an embedding request" ) ;
940- request = JsonSerializer . Deserialize < OpenAIEmbeddingRequest > ( content , ProxyUtils . JsonSerializerOptions ) ;
941- return true ;
942- }
943-
944- // Image generation request
945- if ( rawRequest . TryGetProperty ( "prompt" , out _ ) &&
946- ( rawRequest . TryGetProperty ( "size" , out _ ) || rawRequest . TryGetProperty ( "n" , out _ ) ) )
947- {
948- Logger . LogDebug ( "Request is an image generation request" ) ;
949- request = JsonSerializer . Deserialize < OpenAIImageRequest > ( content , ProxyUtils . JsonSerializerOptions ) ;
950- return true ;
951- }
952-
953- // Audio transcription request
954- if ( rawRequest . TryGetProperty ( "file" , out _ ) )
955- {
956- Logger . LogDebug ( "Request is an audio transcription request" ) ;
957- request = JsonSerializer . Deserialize < OpenAIAudioRequest > ( content , ProxyUtils . JsonSerializerOptions ) ;
958- return true ;
959- }
960-
961- // Audio speech synthesis request
962- if ( rawRequest . TryGetProperty ( "input" , out _ ) && rawRequest . TryGetProperty ( "voice" , out _ ) )
963- {
964- Logger . LogDebug ( "Request is an audio speech synthesis request" ) ;
965- request = JsonSerializer . Deserialize < OpenAIAudioSpeechRequest > ( content , ProxyUtils . JsonSerializerOptions ) ;
966- return true ;
967- }
968-
969- // Fine-tuning request
970- if ( rawRequest . TryGetProperty ( "training_file" , out _ ) )
971- {
972- Logger . LogDebug ( "Request is a fine-tuning request" ) ;
973- request = JsonSerializer . Deserialize < OpenAIFineTuneRequest > ( content , ProxyUtils . JsonSerializerOptions ) ;
974- return true ;
975- }
976-
977- Logger . LogDebug ( "Request is not an OpenAI request." ) ;
978- return false ;
979- }
980- catch ( JsonException ex )
981- {
982- Logger . LogDebug ( ex , "Failed to deserialize OpenAI request." ) ;
983- return false ;
984- }
985- }
986-
987898 private static string GetOperationName ( OpenAIRequest request )
988899 {
989900 if ( request == null )
@@ -1004,63 +915,6 @@ private static string GetOperationName(OpenAIRequest request)
1004915 } ;
1005916 }
1006917
1007- private bool IsStreamingResponse ( Response response )
1008- {
1009- Logger . LogTrace ( "{Method} called" , nameof ( IsStreamingResponse ) ) ;
1010- var contentType = response . Headers . FirstOrDefault ( h => h . Name . Equals ( "content-type" , StringComparison . OrdinalIgnoreCase ) ) ? . Value ;
1011- if ( string . IsNullOrEmpty ( contentType ) )
1012- {
1013- Logger . LogDebug ( "No content-type header found" ) ;
1014- return false ;
1015- }
1016-
1017- var isStreamingResponse = contentType . Contains ( "text/event-stream" , StringComparison . OrdinalIgnoreCase ) ;
1018- Logger . LogDebug ( "IsStreamingResponse: {IsStreamingResponse}" , isStreamingResponse ) ;
1019-
1020- Logger . LogTrace ( "{Method} finished" , nameof ( IsStreamingResponse ) ) ;
1021- return isStreamingResponse ;
1022- }
1023-
1024- private string GetBodyFromStreamingResponse ( Response response )
1025- {
1026- Logger . LogTrace ( "{Method} called" , nameof ( GetBodyFromStreamingResponse ) ) ;
1027-
1028- // default to the whole body
1029- var bodyString = response . BodyString ;
1030-
1031- var chunks = bodyString . Split ( "\n \n " , StringSplitOptions . RemoveEmptyEntries ) ;
1032- if ( chunks . Length == 0 )
1033- {
1034- Logger . LogDebug ( "No chunks found in the response body" ) ;
1035- return bodyString ;
1036- }
1037-
1038- // check if the last chunk is `data: [DONE]`
1039- var lastChunk = chunks . Last ( ) . Trim ( ) ;
1040- if ( lastChunk . Equals ( "data: [DONE]" , StringComparison . OrdinalIgnoreCase ) )
1041- {
1042- // get next to last chunk
1043- var chunk = chunks . Length > 1 ? chunks [ ^ 2 ] . Trim ( ) : string . Empty ;
1044- if ( chunk . StartsWith ( "data: " , StringComparison . OrdinalIgnoreCase ) )
1045- {
1046- // remove the "data: " prefix
1047- bodyString = chunk [ "data: " . Length ..] . Trim ( ) ;
1048- Logger . LogDebug ( "Last chunk starts with 'data: ', using the last chunk as the body: {BodyString}" , bodyString ) ;
1049- }
1050- else
1051- {
1052- Logger . LogDebug ( "Last chunk does not start with 'data: ', using the whole body" ) ;
1053- }
1054- }
1055- else
1056- {
1057- Logger . LogDebug ( "Last chunk is not `data: [DONE]`, using the whole body" ) ;
1058- }
1059-
1060- Logger . LogTrace ( "{Method} finished" , nameof ( GetBodyFromStreamingResponse ) ) ;
1061- return bodyString ;
1062- }
1063-
1064918 public void Dispose ( )
1065919 {
1066920 _loader ? . Dispose ( ) ;
0 commit comments