fix errors

This commit is contained in:
Leonid Pershin
2025-10-17 02:36:47 +03:00
parent 0f28d988c0
commit 5ce7219703
3 changed files with 179 additions and 104 deletions

View File

@@ -9,40 +9,88 @@ namespace ChatBot.Migrations
/// <inheritdoc /> /// <inheritdoc />
public partial class InitialCreate : Migration public partial class InitialCreate : Migration
{ {
private const string ChatSessionsTableName = "chat_sessions";
private const string ChatMessagesTableName = "chat_messages";
private const string ChatSessionsIdColumn = "id";
private const string ChatMessagesSessionIdColumn = "session_id";
private const string IntegerType = "integer";
/// <inheritdoc /> /// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder) protected override void Up(MigrationBuilder migrationBuilder)
{ {
migrationBuilder.CreateTable( migrationBuilder.CreateTable(
name: "chat_sessions", name: ChatSessionsTableName,
columns: table => new columns: table => new
{ {
id = table.Column<int>(type: "integer", nullable: false) id = table
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), .Column<int>(type: IntegerType, nullable: false)
session_id = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: false), .Annotation(
"Npgsql:ValueGenerationStrategy",
NpgsqlValueGenerationStrategy.IdentityByDefaultColumn
),
session_id = table.Column<string>(
type: "character varying(50)",
maxLength: 50,
nullable: false
),
chat_id = table.Column<long>(type: "bigint", nullable: false), chat_id = table.Column<long>(type: "bigint", nullable: false),
chat_type = table.Column<string>(type: "character varying(20)", maxLength: 20, nullable: false), chat_type = table.Column<string>(
chat_title = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: false), type: "character varying(20)",
model = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: false), maxLength: 20,
created_at = table.Column<DateTime>(type: "timestamp with time zone", nullable: false), nullable: false
last_updated_at = table.Column<DateTime>(type: "timestamp with time zone", nullable: false), ),
max_history_length = table.Column<int>(type: "integer", nullable: false) chat_title = table.Column<string>(
type: "character varying(200)",
maxLength: 200,
nullable: false
),
model = table.Column<string>(
type: "character varying(100)",
maxLength: 100,
nullable: false
),
created_at = table.Column<DateTime>(
type: "timestamp with time zone",
nullable: false
),
last_updated_at = table.Column<DateTime>(
type: "timestamp with time zone",
nullable: false
),
max_history_length = table.Column<int>(type: IntegerType, nullable: false),
}, },
constraints: table => constraints: table =>
{ {
table.PrimaryKey("PK_chat_sessions", x => x.id); table.PrimaryKey("PK_chat_sessions", x => x.id);
}); }
);
migrationBuilder.CreateTable( migrationBuilder.CreateTable(
name: "chat_messages", name: ChatMessagesTableName,
columns: table => new columns: table => new
{ {
id = table.Column<int>(type: "integer", nullable: false) id = table
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), .Column<int>(type: IntegerType, nullable: false)
session_id = table.Column<int>(type: "integer", nullable: false), .Annotation(
content = table.Column<string>(type: "character varying(10000)", maxLength: 10000, nullable: false), "Npgsql:ValueGenerationStrategy",
role = table.Column<string>(type: "character varying(20)", maxLength: 20, nullable: false), NpgsqlValueGenerationStrategy.IdentityByDefaultColumn
created_at = table.Column<DateTime>(type: "timestamp with time zone", nullable: false), ),
message_order = table.Column<int>(type: "integer", nullable: false) session_id = table.Column<int>(type: IntegerType, nullable: false),
content = table.Column<string>(
type: "character varying(10000)",
maxLength: 10000,
nullable: false
),
role = table.Column<string>(
type: "character varying(20)",
maxLength: 20,
nullable: false
),
created_at = table.Column<DateTime>(
type: "timestamp with time zone",
nullable: false
),
message_order = table.Column<int>(type: IntegerType, nullable: false),
}, },
constraints: table => constraints: table =>
{ {
@@ -50,46 +98,51 @@ namespace ChatBot.Migrations
table.ForeignKey( table.ForeignKey(
name: "FK_chat_messages_chat_sessions_session_id", name: "FK_chat_messages_chat_sessions_session_id",
column: x => x.session_id, column: x => x.session_id,
principalTable: "chat_sessions", principalTable: ChatSessionsTableName,
principalColumn: "id", principalColumn: ChatSessionsIdColumn,
onDelete: ReferentialAction.Cascade); onDelete: ReferentialAction.Cascade
}); );
}
);
migrationBuilder.CreateIndex( migrationBuilder.CreateIndex(
name: "IX_chat_messages_created_at", name: "IX_chat_messages_created_at",
table: "chat_messages", table: ChatMessagesTableName,
column: "created_at"); column: "created_at"
);
migrationBuilder.CreateIndex( migrationBuilder.CreateIndex(
name: "IX_chat_messages_session_id", name: "IX_chat_messages_session_id",
table: "chat_messages", table: ChatMessagesTableName,
column: "session_id"); column: ChatMessagesSessionIdColumn
);
migrationBuilder.CreateIndex( migrationBuilder.CreateIndex(
name: "IX_chat_messages_session_id_message_order", name: "IX_chat_messages_session_id_message_order",
table: "chat_messages", table: ChatMessagesTableName,
columns: new[] { "session_id", "message_order" }); columns: new[] { ChatMessagesSessionIdColumn, "message_order" }
);
migrationBuilder.CreateIndex( migrationBuilder.CreateIndex(
name: "IX_chat_sessions_chat_id", name: "IX_chat_sessions_chat_id",
table: "chat_sessions", table: ChatSessionsTableName,
column: "chat_id"); column: "chat_id"
);
migrationBuilder.CreateIndex( migrationBuilder.CreateIndex(
name: "IX_chat_sessions_session_id", name: "IX_chat_sessions_session_id",
table: "chat_sessions", table: ChatSessionsTableName,
column: "session_id", column: "session_id",
unique: true); unique: true
);
} }
/// <inheritdoc /> /// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder) protected override void Down(MigrationBuilder migrationBuilder)
{ {
migrationBuilder.DropTable( migrationBuilder.DropTable(name: ChatMessagesTableName);
name: "chat_messages");
migrationBuilder.DropTable( migrationBuilder.DropTable(name: ChatSessionsTableName);
name: "chat_sessions");
} }
} }
} }

View File

@@ -288,94 +288,116 @@ namespace ChatBot.Services
CancellationToken cancellationToken CancellationToken cancellationToken
) )
{ {
const int maxRetries = 2; // Fewer retries for summarization to avoid delays const int maxRetries = 2;
for (int attempt = 1; attempt <= maxRetries; attempt++) for (int attempt = 1; attempt <= maxRetries; attempt++)
{ {
try try
{ {
var chatRequest = new OllamaSharp.Models.Chat.ChatRequest return await TryGenerateSummaryAsync(messages, cancellationToken);
{
Messages = messages
.Select(m => new OllamaSharp.Models.Chat.Message(m.Role, m.Content))
.ToList(),
Stream = false,
Options = new OllamaSharp.Models.RequestOptions
{
Temperature = 0.3f, // Lower temperature for more focused summaries
},
};
// Create timeout cancellation token for compression operations
using var timeoutCts = new CancellationTokenSource(
TimeSpan.FromSeconds(_aiSettings.CompressionTimeoutSeconds)
);
using var combinedCts = CancellationTokenSource.CreateLinkedTokenSource(
cancellationToken,
timeoutCts.Token
);
var response = new StringBuilder();
await foreach (
var chatResponse in _ollamaClient
.ChatAsync(chatRequest)
.WithCancellation(combinedCts.Token)
)
{
if (chatResponse?.Message?.Content != null)
{
response.Append(chatResponse.Message.Content);
}
}
var result = response.ToString().Trim();
return result.Length > _aiSettings.MaxSummarizedMessageLength
? result.Substring(0, _aiSettings.MaxSummarizedMessageLength) + "..."
: result;
} }
catch (OperationCanceledException ex) catch (OperationCanceledException ex)
when (ex.InnerException is TaskCanceledException) when (ex.InnerException is TaskCanceledException)
{ {
_logger.LogWarning( if (HandleTimeoutAsync(attempt, maxRetries, ex))
ex,
"Compression operation timed out after {TimeoutSeconds} seconds on attempt {Attempt}",
_aiSettings.CompressionTimeoutSeconds,
attempt
);
if (attempt == maxRetries)
{
return string.Empty; return string.Empty;
}
} }
catch (HttpRequestException ex) when (attempt < maxRetries) catch (HttpRequestException ex) when (attempt < maxRetries)
{ {
var delay = 1000 * attempt; // Simple linear backoff for summarization await HandleHttpExceptionAsync(attempt, maxRetries, ex, cancellationToken);
_logger.LogWarning(
ex,
"Failed to generate AI summary on attempt {Attempt}/{MaxAttempts}. Retrying in {DelayMs}ms...",
attempt,
maxRetries,
delay
);
await Task.Delay(delay, cancellationToken);
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.LogWarning( if (HandleGenericExceptionAsync(attempt, maxRetries, ex))
ex,
"Failed to generate AI summary on attempt {Attempt}",
attempt
);
if (attempt == maxRetries)
{
return string.Empty; return string.Empty;
}
} }
} }
return string.Empty; return string.Empty;
} }
private async Task<string> TryGenerateSummaryAsync(
List<ChatMessage> messages,
CancellationToken cancellationToken
)
{
var chatRequest = CreateSummaryRequest(messages);
using var timeoutCts = new CancellationTokenSource(
TimeSpan.FromSeconds(_aiSettings.CompressionTimeoutSeconds)
);
using var combinedCts = CancellationTokenSource.CreateLinkedTokenSource(
cancellationToken,
timeoutCts.Token
);
var response = new StringBuilder();
await foreach (
var chatResponse in _ollamaClient
.ChatAsync(chatRequest)
.WithCancellation(combinedCts.Token)
)
{
if (chatResponse?.Message?.Content != null)
{
response.Append(chatResponse.Message.Content);
}
}
var result = response.ToString().Trim();
return result.Length > _aiSettings.MaxSummarizedMessageLength
? result.Substring(0, _aiSettings.MaxSummarizedMessageLength) + "..."
: result;
}
private static OllamaSharp.Models.Chat.ChatRequest CreateSummaryRequest(
List<ChatMessage> messages
)
{
return new OllamaSharp.Models.Chat.ChatRequest
{
Messages = messages
.Select(m => new OllamaSharp.Models.Chat.Message(m.Role, m.Content))
.ToList(),
Stream = false,
Options = new OllamaSharp.Models.RequestOptions { Temperature = 0.3f },
};
}
private bool HandleTimeoutAsync(int attempt, int maxRetries, OperationCanceledException ex)
{
_logger.LogWarning(
ex,
"Compression operation timed out after {TimeoutSeconds} seconds on attempt {Attempt}",
_aiSettings.CompressionTimeoutSeconds,
attempt
);
return attempt == maxRetries;
}
private async Task HandleHttpExceptionAsync(
int attempt,
int maxRetries,
HttpRequestException ex,
CancellationToken cancellationToken
)
{
var delay = 1000 * attempt;
_logger.LogWarning(
ex,
"Failed to generate AI summary on attempt {Attempt}/{MaxAttempts}. Retrying in {DelayMs}ms...",
attempt,
maxRetries,
delay
);
await Task.Delay(delay, cancellationToken);
}
private bool HandleGenericExceptionAsync(int attempt, int maxRetries, Exception ex)
{
_logger.LogWarning(ex, "Failed to generate AI summary on attempt {Attempt}", attempt);
return attempt == maxRetries;
}
/// <summary> /// <summary>
/// Check if a single message should be kept (not too short, not too long) /// Check if a single message should be kept (not too short, not too long)
/// </summary> /// </summary>

View File

@@ -53,7 +53,7 @@ namespace ChatBot.Services.Telegram.Commands
/// <summary> /// <summary>
/// Проверяет, есть ли аргументы у команды /// Проверяет, есть ли аргументы у команды
/// </summary> /// </summary>
protected bool HasArguments(TelegramCommandContext context) protected static bool HasArguments(TelegramCommandContext context)
{ {
return !string.IsNullOrWhiteSpace(context.Arguments); return !string.IsNullOrWhiteSpace(context.Arguments);
} }
@@ -61,7 +61,7 @@ namespace ChatBot.Services.Telegram.Commands
/// <summary> /// <summary>
/// Получает аргументы команды /// Получает аргументы команды
/// </summary> /// </summary>
protected string GetArguments(TelegramCommandContext context) protected static string GetArguments(TelegramCommandContext context)
{ {
return context.Arguments; return context.Arguments;
} }