Compare commits

...

3 Commits

Author SHA1 Message Date
5418489f77 🐛 Fix function call bug, for real this time 2025-11-16 00:52:02 +08:00
310f2c1497 🐛 Fix function call in chat history issue 2025-11-16 00:34:31 +08:00
0ae8a2cfd4 🐛 Fix function calls in thought 2025-11-15 23:43:22 +08:00
5 changed files with 220 additions and 35 deletions

View File

@@ -0,0 +1,142 @@
// <auto-generated />
using System;
using System.Collections.Generic;
using DysonNetwork.Insight;
using DysonNetwork.Shared.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using NodaTime;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace DysonNetwork.Insight.Migrations
{
[DbContext(typeof(AppDatabase))]
[Migration("20251115162347_UpdatedFunctionCallModels")]
partial class UpdatedFunctionCallModels
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "9.0.11")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("DysonNetwork.Shared.Models.SnThinkingSequence", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("id");
b.Property<Guid>("AccountId")
.HasColumnType("uuid")
.HasColumnName("account_id");
b.Property<Instant>("CreatedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at");
b.Property<Instant?>("DeletedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("deleted_at");
b.Property<long>("PaidToken")
.HasColumnType("bigint")
.HasColumnName("paid_token");
b.Property<string>("Topic")
.HasMaxLength(4096)
.HasColumnType("character varying(4096)")
.HasColumnName("topic");
b.Property<long>("TotalToken")
.HasColumnType("bigint")
.HasColumnName("total_token");
b.Property<Instant>("UpdatedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at");
b.HasKey("Id")
.HasName("pk_thinking_sequences");
b.ToTable("thinking_sequences", (string)null);
});
modelBuilder.Entity("DysonNetwork.Shared.Models.SnThinkingThought", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid")
.HasColumnName("id");
b.Property<Instant>("CreatedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("created_at");
b.Property<Instant?>("DeletedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("deleted_at");
b.Property<List<SnCloudFileReferenceObject>>("Files")
.IsRequired()
.HasColumnType("jsonb")
.HasColumnName("files");
b.Property<string>("ModelName")
.HasMaxLength(4096)
.HasColumnType("character varying(4096)")
.HasColumnName("model_name");
b.Property<List<SnThinkingMessagePart>>("Parts")
.IsRequired()
.HasColumnType("jsonb")
.HasColumnName("parts");
b.Property<int>("Role")
.HasColumnType("integer")
.HasColumnName("role");
b.Property<Guid>("SequenceId")
.HasColumnType("uuid")
.HasColumnName("sequence_id");
b.Property<long>("TokenCount")
.HasColumnType("bigint")
.HasColumnName("token_count");
b.Property<Instant>("UpdatedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at");
b.HasKey("Id")
.HasName("pk_thinking_thoughts");
b.HasIndex("SequenceId")
.HasDatabaseName("ix_thinking_thoughts_sequence_id");
b.ToTable("thinking_thoughts", (string)null);
});
modelBuilder.Entity("DysonNetwork.Shared.Models.SnThinkingThought", b =>
{
b.HasOne("DysonNetwork.Shared.Models.SnThinkingSequence", "Sequence")
.WithMany()
.HasForeignKey("SequenceId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_thinking_thoughts_thinking_sequences_sequence_id");
b.Navigation("Sequence");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,22 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace DysonNetwork.Insight.Migrations
{
/// <inheritdoc />
public partial class UpdatedFunctionCallModels : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
}
}
}

View File

@@ -112,12 +112,12 @@ public class ThoughtController(ThoughtProvider provider, ThoughtService service)
// Add previous thoughts (excluding the current user thought, which is the first one since descending)
var previousThoughts = await service.GetPreviousThoughtsAsync(sequence);
var count = previousThoughts.Count;
for (var i = 1; i < count; i++) // skip first (the newest, current user)
for (var i = count - 1; i >= 1; i--) // skip first (the newest, current user)
{
var thought = previousThoughts[i];
var textContent = new StringBuilder();
var functionCalls = new List<FunctionCallContent>();
var hasFunctionCalls = false;
var functionResults = new List<FunctionResultContent>();
foreach (var part in thought.Parts)
{
@@ -127,16 +127,30 @@ public class ThoughtController(ThoughtProvider provider, ThoughtService service)
textContent.Append(part.Text);
break;
case ThinkingMessagePartType.FunctionCall:
hasFunctionCalls = true;
functionCalls.Add(new FunctionCallContent(part.FunctionCall!.Name, part.FunctionCall.Arguments,
part.FunctionCall.Id));
var arguments = !string.IsNullOrEmpty(part.FunctionCall!.Arguments)
? JsonSerializer.Deserialize<Dictionary<string, object?>>(part.FunctionCall!.Arguments)
: null;
var kernelArgs = arguments is not null ? new KernelArguments(arguments) : null;
functionCalls.Add(new FunctionCallContent(
functionName: part.FunctionCall!.Name,
pluginName: part.FunctionCall.PluginName,
id: part.FunctionCall.Id,
arguments: kernelArgs
));
break;
case ThinkingMessagePartType.FunctionResult:
var resultObject = part.FunctionResult!.Result;
var resultString = resultObject is string s ? s : JsonSerializer.Serialize(resultObject);
var result = new FunctionResultContent(part.FunctionResult!.CallId, resultString);
chatHistory.Add(result.ToChatMessage());
var resultString = resultObject as string ?? JsonSerializer.Serialize(resultObject);
functionResults.Add(new FunctionResultContent(
callId: part.FunctionResult.CallId,
functionName: part.FunctionResult.FunctionName,
pluginName: part.FunctionResult.PluginName,
result: resultString
));
break;
default:
throw new ArgumentOutOfRangeException();
}
}
@@ -147,9 +161,9 @@ public class ThoughtController(ThoughtProvider provider, ThoughtService service)
else
{
var assistantMessage = new ChatMessageContent(AuthorRole.Assistant, textContent.ToString());
if (hasFunctionCalls)
if (functionCalls.Count > 0)
{
assistantMessage.Items = new ChatMessageContentItemCollection();
assistantMessage.Items = [];
foreach (var fc in functionCalls)
{
assistantMessage.Items.Add(fc);
@@ -157,6 +171,14 @@ public class ThoughtController(ThoughtProvider provider, ThoughtService service)
}
chatHistory.Add(assistantMessage);
if (functionResults.Count > 0)
{
foreach (var fr in functionResults)
{
chatHistory.Add(fr.ToChatMessage());
}
}
}
}
@@ -178,8 +200,10 @@ public class ThoughtController(ThoughtProvider provider, ThoughtService service)
AuthorRole? authorRole = null;
var functionCallBuilder = new FunctionCallContentBuilder();
await foreach (var streamingContent in chatCompletionService.GetStreamingChatMessageContentsAsync(
chatHistory, executionSettings, kernel))
await foreach (
var streamingContent in chatCompletionService.GetStreamingChatMessageContentsAsync(
chatHistory, executionSettings, kernel)
)
{
authorRole ??= streamingContent.Role;
@@ -192,18 +216,7 @@ public class ThoughtController(ThoughtProvider provider, ThoughtService service)
await Response.Body.FlushAsync();
}
if (streamingContent.Items.Count > 0)
{
functionCallBuilder.Append(streamingContent);
}
foreach (var functionCallUpdate in streamingContent.Items.OfType<StreamingFunctionCallUpdateContent>())
{
var messageJson = JsonSerializer.Serialize(new
{ type = "function_call_update", data = functionCallUpdate });
await Response.Body.WriteAsync(Encoding.UTF8.GetBytes($"data: {messageJson}\n\n"));
await Response.Body.FlushAsync();
}
functionCallBuilder.Append(streamingContent);
}
var finalMessageText = textContentBuilder.ToString();
@@ -213,19 +226,21 @@ public class ThoughtController(ThoughtProvider provider, ThoughtService service)
{ Type = ThinkingMessagePartType.Text, Text = finalMessageText });
}
var functionCalls = functionCallBuilder.Build();
var functionCalls = functionCallBuilder.Build()
.Where(fc => !string.IsNullOrEmpty(fc.Id)).ToList();
if (functionCalls.Count == 0)
{
break;
}
var assistantMessage = new ChatMessageContent(authorRole ?? AuthorRole.Assistant,
string.IsNullOrEmpty(finalMessageText) ? null : finalMessageText);
var assistantMessage = new ChatMessageContent(
authorRole ?? AuthorRole.Assistant,
string.IsNullOrEmpty(finalMessageText) ? null : finalMessageText
);
foreach (var functionCall in functionCalls)
{
assistantMessage.Items.Add(functionCall);
}
chatHistory.Add(assistantMessage);
foreach (var functionCall in functionCalls)
@@ -236,7 +251,8 @@ public class ThoughtController(ThoughtProvider provider, ThoughtService service)
FunctionCall = new SnFunctionCall
{
Id = functionCall.Id!,
Name = functionCall.FunctionName!,
PluginName = functionCall.PluginName,
Name = functionCall.FunctionName,
Arguments = JsonSerializer.Serialize(functionCall.Arguments)
}
};
@@ -263,7 +279,9 @@ public class ThoughtController(ThoughtProvider provider, ThoughtService service)
Type = ThinkingMessagePartType.FunctionResult,
FunctionResult = new SnFunctionResult
{
CallId = resultContent.CallId,
CallId = resultContent.CallId!,
PluginName = resultContent.PluginName,
FunctionName = resultContent.FunctionName,
Result = resultContent.Result!,
IsError = resultContent.Result is Exception
}

View File

@@ -79,8 +79,8 @@ public class ThoughtProvider
builder.Services.AddAccountService();
builder.Services.AddSphereService();
builder.Plugins.AddFromType<SnAccountKernelPlugin>();
builder.Plugins.AddFromType<SnPostKernelPlugin>();
builder.Plugins.AddFromObject(new SnAccountKernelPlugin(_accountClient));
builder.Plugins.AddFromObject(new SnPostKernelPlugin(_postClient));
return builder.Build();
}
@@ -116,12 +116,12 @@ public class ThoughtProvider
case "ollama":
return new OllamaPromptExecutionSettings
{
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto()
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto(autoInvoke: false)
};
case "deepseek":
return new OpenAIPromptExecutionSettings
{
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto()
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto(autoInvoke: false)
};
default:
throw new InvalidOperationException("Unknown provider: " + ModelProviderType);

View File

@@ -39,6 +39,7 @@ public class SnThinkingMessagePart
public class SnFunctionCall
{
public string Id { get; set; } = null!;
public string? PluginName { get; set; }
public string Name { get; set; } = null!;
public string Arguments { get; set; } = null!;
}
@@ -46,6 +47,8 @@ public class SnFunctionCall
public class SnFunctionResult
{
public string CallId { get; set; } = null!;
public string? PluginName { get; set; }
public string FunctionName { get; set; } = null!;
public object Result { get; set; } = null!;
public bool IsError { get; set; }
}