(Übersetzt aus dem Englischen: /en/automatic-blogpost-translation-with-claude-ai )

Um ganz ehrlich zu sein: Ich mag automatische Übersetzungen nicht besonders. Besonders wenn man nicht in einem englischsprachigen Land geboren wurde, waren Übersetzungen von Anfang an eine echte Qual. MSDN war einer der Kandidaten, die wirklich, wirklich schlechte Übersetzungen lieferten, Jahre bevor KI ein Gesprächsthema war.

Jetzt mit KI werden die Übersetzungen besser, aber es gibt immer noch schlechte und zu viele Inhalte werden übersetzt. YouTube ist von Deutschland aus kaum noch nutzbar, wenn man an Originalinhalten interessiert ist.

Welchen Weg einschlagen?

Trotz dieser Vorbehalte denke ich, dass eine Übersetzung durchaus nützlich sein kann. Erstens muss sie gut sein. Und die zweite Anforderung ist, dass Nutzer eine Wahl haben.

Also habe ich beschlossen, Übersetzungen zu meinem Blog hinzuzufügen. 2019 habe ich aufgehört, in zwei Sprachen zu bloggen, daher waren alle deutschen Einträge sehr alt. Zeit zu prüfen, ob es gute Lösungen gibt.

Da ich ein Buch übersetzt habe, hatte ich bereits einige Vergleiche. Zumindest vom Deutschen ins Englische war Claude.io bisher mein Favorit. Die Qualität von DeepL gefiel mir nicht wirklich und ChatGPT hatte zwar gute Ergebnisse, war aber sehr langsam. Außerdem: Claude scheint Markdown wirklich zu mögen. Was in diesem Fall hilfreich ist.

Diesmal wollte ich die API nutzen, um meine Grav-Blogeinträge automatisch zu übersetzen. Die schlechte Nachricht ist: Dafür brauchst du einen kostenpflichtigen Account. Die gute Nachricht ist: Es spielt nicht wirklich eine Rolle, wie viel Geld du in dein Wallet einzahlst und es ist recht günstig.

Meine 50KB an Daten kosteten etwa 20 Cent. Natürlich kann das bei dir anders sein. Dies ist übrigens nicht gesponsert.

Vorbereitungen

Nach der Erstellung eines Accounts kannst du das NuGet-Paket Anthrophic.SDK verwenden. Du erstellst einfach einen neuen Client mit deinem Secret und kannst mit einigen Nachrichten beginnen. Wenn du jemals mit der OpenAi-API oder Llama gearbeitet hast, ist das Erstellen der Nachricht fast das Gleiche:

  var client = new AnthropicClient(new APIAuthentication(_apiSecret));
var messages = new List<Message>()
{
   new Message(RoleType.User, "Bitte übersetze das folgende Markdown nach Deutsch.");

),
new Message(RoleType.User, markdown)
};

Falls du neugierig bist, die Nachricht sagt einfach, dass ein Markdown ins Deutsche übersetzt werden soll

Dann fügst du die Parameter hinzu. Temperature ist - wie bei KI-APIs bekannt - wie "kreativ" die KI werden darf. MaxTokens ist ein erforderlicher Wert und kann nicht zu hoch sein:

        var pars = new MessageParameters()
        {
            Messages = messages,
            MaxTokens = 1024,
            Model = AnthropicModels.Claude35Sonnet,
            Stream = false,
            Temperature = 1m,
        };

Die Antwort zu bekommen ist jetzt einfach:

var response = await client.Messages.GetClaudeMessageAsync(pars);

Was ich nicht mag: Wenn eWas ich nicht mag: Wenn etwas mit deinem Account nicht stimmt, erhältst du keinen nützlichen Fehlercode, sondern einfach null. Dieses Problem hatte ich, bevor ich Geld eingezahlt habe.

Die Antwort ist jetzt in response.Message. Das Ergebnis ist großartig, aber lange Texte hören nach einer Weile einfach auf. Response.StopReason sagt uns warum:

max_tokens

Die offensichtliche Lösung wäre, die erlaubten Tokens zu erhöhen, aber wie gesagt hat MaxTokens einen begrenzten Bereich. Also musst du diesen Fehler abfangen und in eine Schleife gehen. Das Lustige ist: Claude "vergisst", was er vorher gesagt hat. Also musst du es ihm sagen und danach bitten fortzufahren:

 while (response.StopReason == "max_tokens")
 {
   pars.Messages.Add(new Message(RoleType.Assistant, response.Message));
   pars.Messages.Add(new Message(RoleType.User, "bitte fahre fort"));
   response = await client.Messages.GetClaudeMessageAsync(pars);
}

Das war's. Letztendlich nicht wirklich so kompliziert.

KI macht KI-Dinge

Während das Ergebnis recht gut ist, hatte ich einige Probleme. Als Programmierer ist es eine gewisse Lernkurve, dass Fehler nicht durch Programmierung behoben werden können, sondern nur durch Nachrichten. Das erste war, dass die Übersetzung ziemlich formal war. Eine zusätzliche Nachricht änderte das:

pars.Messages.Add(new Message(RoleType.User, "Enthält der Text eine persönliche Anrede, verwende 'Du' und nicht 'Sie'. ");

(Ich werde diesen Befehl nicht übersetzen können. Das ist eine sehr deutsche Sache :) )

Ein anderes Problem ist, dass Claude gerne Dinge erklärt. Also müssen wir ihm sagen, damit aufzuhören:

pars.Messages.Add(new Message(RoleType.User, "Antworte ausschließlich mit dem Dokument, ergänze keine Erklärungen. ");

(Was übersetzt bedeutet 'Antworte nur mit dem Dokument, erkläre nichts')

Überraschenderweise musste ich keine zusätzliche Nachricht hinzufügen, um Code nicht zu übersetzen. Wenn du zum Beispiel apt-get install ins Deutsche als apt-get installiere übersetzt, würde das nicht funktionieren. Ich habe das auf anderen Seiten gesehen, aber Claude brauchte keine zusätzlichen Hinweise.

Andererseits hat Claude manchmal entschieden, dass mein Code nicht interessant genug war:

[Rest des Dokuments enthält die gleichen Code-Blöcke und technischen Anweisungen wie im Original, nur der erklärende Text wurde ins Deutsche übersetzt]

Was grob übersetzt bedeutet "der Rest des Dokuments enthält den gleichen Code wie das Original. Habe das nicht übersetzt". Das hatte ich nicht erwartet, aber auch das war behebbar:

pars.Messages.Add(new Message(RoleType.User, "Fasse keine Inhalte zusammen, sondern gebe alles aus. Ziel soll sein dass man das deutschsprachige Dokument genauso alleine versteht wie das englischsprachige "):   

was grob übersetzt bedeutet "Fasse keine Inhalte zusammen. Das Ziel sollte sein, dass das deutsche Dokument eigenständig verstanden werden kann"

Jetzt ist das Ergebnis genau wie benötigt. Der Vollständigkeit halber hier der komplette Code:

private async Task<string> TranslateSingleFile(string source)
{
   var translated=string.Empty;
   var markdown = File.ReadAllText(```
private async Task<string> TranslateSingleFile(string source)
{
   var translated=string.Empty;
   var markdown = File.ReadAllText(source);
   var client = new AnthropicClient(new APIAuthentication(_apiSecret));
   var messages = new List<Message>()
   {
      new Message(RoleType.User, "Bitte übersetze das folgende Markdown nach Deutsch."
      +" Antworte ausschließlich mit dem Dokument, ergänze keine Erklärungen. "
      + "Enthält der Text eine persönliche Anrede, verwende 'Du' und nicht 'Sie'. "
      +"Fasse keine Inhalte zusammen, sondern gebe alles aus. Ziel soll sein dass man das deutschsprachige Dokument genauso alleine versteht wie das englischsprachige "            
       ),
       new Message(RoleType.User, markdown)
   };

   var pars = new MessageParameters()
   {
      Messages = messages,
      MaxTokens = 1024,
      Model = AnthropicModels.Claude35Sonnet,
      Stream = false,
      Temperature = 1m,
   };

   var response = await client.Messages.GetClaudeMessageAsync(pars);
   if (response?.Message == null) throw new Exception("No valid response from Claude");

   translated += response.Message.ToString();
   while (response.StopReason == "max_tokens")
   {      
      pars.Messages.Add(new Message(RoleType.Assistant, response.Message));
      pars.Messages.Add(new Message(RoleType.User, "bitte fahre fort"));
      response = await client.Messages.GetClaudeMessageAsync(pars);     
      translated += response.Message.ToString();
   }

   if (response.StopReason != "end_turn") throw new Exception($"Translation failed. Stop reason:{response.StopReason}");
   return translated;
}