fix errors
This commit is contained in:
@@ -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");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user