|
1 | 1 | using System.Collections.Concurrent; |
2 | 2 | using System.Text; |
3 | 3 | using System.Text.Json; |
4 | | -using System.Text.Json.Serialization; |
5 | 4 | using LLama; |
6 | 5 | using LLama.Batched; |
7 | 6 | using LLama.Common; |
@@ -37,11 +36,6 @@ public class LLMService : ILLMService |
37 | 36 | private readonly IMemoryFactory memoryFactory; |
38 | 37 | private readonly string modelsPath; |
39 | 38 |
|
40 | | - private readonly JsonSerializerOptions _jsonToolOptions = new() |
41 | | - { |
42 | | - PropertyNameCaseInsensitive = true, |
43 | | - }; |
44 | | - |
45 | 39 | public LLMService( |
46 | 40 | MaINSettings options, |
47 | 41 | INotificationService notificationService, |
@@ -391,105 +385,6 @@ private static string FormatToolsForPrompt(ToolsConfiguration toolsConfig) |
391 | 385 | """; |
392 | 386 | } |
393 | 387 |
|
394 | | - private ToolParseResult ParseToolCalls(string response) |
395 | | - { |
396 | | - if (string.IsNullOrWhiteSpace(response)) |
397 | | - return ToolParseResult.Failure("Response is empty."); |
398 | | - |
399 | | - var jsonContent = ExtractJsonContent(response); |
400 | | - |
401 | | - if (string.IsNullOrEmpty(jsonContent)) |
402 | | - return ToolParseResult.ToolNotFound(); |
403 | | - |
404 | | - try |
405 | | - { |
406 | | - var wrapper = JsonSerializer.Deserialize<ToolResponseWrapper>(jsonContent, _jsonToolOptions); |
407 | | - |
408 | | - if (wrapper?.ToolCalls != null && wrapper.ToolCalls.Any()) |
409 | | - return ToolParseResult.Success(NormalizeToolCalls(wrapper.ToolCalls)); |
410 | | - |
411 | | - return ToolParseResult.Failure("JSON parsed correctly but 'tool_calls' property is missing or empty."); |
412 | | - } |
413 | | - catch (JsonException ex) |
414 | | - { |
415 | | - return ToolParseResult.Failure($"Invalid JSON format: {ex.Message}"); |
416 | | - } |
417 | | - } |
418 | | - |
419 | | - private static string? ExtractJsonContent(string text) |
420 | | - { |
421 | | - text = text.Trim(); |
422 | | - |
423 | | - var firstBrace = text.IndexOf('{'); |
424 | | - var firstBracket = text.IndexOf('['); |
425 | | - var startIndex = (firstBrace >= 0 && firstBracket >= 0) ? Math.Min(firstBrace, firstBracket) : Math.Max(firstBrace, firstBracket); |
426 | | - |
427 | | - var lastBrace = text.LastIndexOf('}'); |
428 | | - var lastBracket = text.LastIndexOf(']'); |
429 | | - var endIndex = Math.Max(lastBrace, lastBracket); |
430 | | - |
431 | | - if (startIndex >= 0 && endIndex > startIndex) |
432 | | - return text.Substring(startIndex, endIndex - startIndex + 1); |
433 | | - |
434 | | - return null; |
435 | | - } |
436 | | - |
437 | | - private static List<ToolCall> NormalizeToolCalls(List<ToolCall>? calls) |
438 | | - { |
439 | | - if (calls == null) |
440 | | - return []; |
441 | | - |
442 | | - foreach (var call in calls) |
443 | | - { |
444 | | - if (string.IsNullOrEmpty(call.Id)) |
445 | | - call.Id = Guid.NewGuid().ToString()[..8]; |
446 | | - |
447 | | - if (string.IsNullOrEmpty(call.Type)) |
448 | | - call.Type = "function"; |
449 | | - |
450 | | - call.Function ??= new FunctionCall(); |
451 | | - } |
452 | | - return calls; |
453 | | - } |
454 | | - |
455 | | - public class ToolCall |
456 | | - { |
457 | | - [JsonPropertyName("id")] |
458 | | - public string Id { get; set; } = Guid.NewGuid().ToString(); |
459 | | - |
460 | | - [JsonPropertyName("type")] |
461 | | - public string Type { get; set; } = "function"; |
462 | | - |
463 | | - [JsonPropertyName("function")] |
464 | | - public FunctionCall Function { get; set; } = new(); |
465 | | - } |
466 | | - |
467 | | - public class FunctionCall |
468 | | - { |
469 | | - [JsonPropertyName("name")] |
470 | | - public string Name { get; set; } = string.Empty; |
471 | | - |
472 | | - [JsonPropertyName("arguments")] |
473 | | - public string Arguments { get; set; } = "{}"; |
474 | | - } |
475 | | - |
476 | | - private class ToolResponseWrapper |
477 | | - { |
478 | | - [JsonPropertyName("tool_calls")] |
479 | | - public List<ToolCall>? ToolCalls { get; set; } |
480 | | - } |
481 | | - |
482 | | - private record ToolParseResult |
483 | | - { |
484 | | - public bool IsSuccess { get; init; } |
485 | | - public List<ToolCall>? ToolCalls { get; init; } |
486 | | - public string? ErrorMessage { get; init; } |
487 | | - |
488 | | - public static ToolParseResult Success(List<ToolCall> calls) => new() { IsSuccess = true, ToolCalls = calls }; |
489 | | - public static ToolParseResult Failure(string error) => new() { IsSuccess = false, ErrorMessage = error }; |
490 | | - public static ToolParseResult ToolNotFound() => new() { IsSuccess = false }; |
491 | | - } |
492 | | - |
493 | 388 | private async Task<(List<LLMTokenValue> Tokens, bool IsComplete, bool HasFailed)> ProcessTokens( |
494 | 389 | Chat chat, |
495 | 390 | Conversation conversation, |
@@ -665,7 +560,7 @@ private async Task<ChatResult> ProcessWithToolsAsync( |
665 | 560 | }; |
666 | 561 | chat.Messages.Add(responseMessage.MarkProcessed()); |
667 | 562 |
|
668 | | - var parseResult = ParseToolCalls(lastResponse); |
| 563 | + var parseResult = ToolCallParser.ParseToolCalls(lastResponse); |
669 | 564 |
|
670 | 565 | // Tool not found or invalid JSON |
671 | 566 | if (!parseResult.IsSuccess) |
|
0 commit comments