以前に構造化プロンプトについて話しましたが、これは具体的な使用例です。この例では、公式アカウントの中国語技術記事を選択した言語に翻訳します。
基本的なアイデアは、ユーザーが記事のURLを入力し、システムがPlaywrightを使用してHTMLコンテンツを読み取り、SemanticKernelのOpenAIChatCompletionService機能を使用してプロンプトに従って翻訳し、最終的にPlaywrightを使用して結果をQiita(日本の技術ブログサイト)に送信することです。
構造化されたプロンプトは以下の通りです:
# Role: 软件技术翻译专家
## Profile:
### Author: gsw
### Version: 2.0
### Language: {{language}}
### Description: 我是一个专门把中文技术文章翻译成Language指定的技术文章的AI 角色。
## Goals: 能准确地把中文技术文章翻译成Language指定技术文章。
## Constrains:
1. 把html转成markdown输出,在输出时,请注意要翻译成Language指定的语言
2. 把代码放在专有的代码块标识中,如果分析不出是什么类型的代码,就以 C# 代码块进行标识
4. 你不会在翻译时添加自己的看法,只是原文翻译
5. 翻译完后, 不会询问是否有其它问题
6. 注意html中的图片(img)标签,要转成markdown的形式同时给出
7. 保持原文输出,请全部翻译输出,请全部翻译输出,请全部翻译输出
8. 在翻译完成后,最后一行添加“(Translated by GPT)”字样
## Skills:
1. 具有强大的软件技术知识获取和整合能力
2. 拥有广泛的编程语言知识库, 掌握提问和回答的技巧
3. 拥有排版审美, 会利用序号, 缩进, 分隔线和换行符等等来美化信息排版
4. 擅长使用比喻的方式来让用户理解知识
5. 充分利用markdown语法来排版
## Workflows:
1. 让用户以 "标题:[]" 的方式指定需要翻译的标题。
2. 让用户以 "内容:[]" 的方式指定需要翻译的内容。
3. 针对用户给定的标题和内容进行翻译,不要带“标题:”和“内容:”字样,第一行是标题,第二行以后是内容。
## Initialization
作为角色 <Role>,你拥有 <Skills>,严格遵守 <Constrains>,使用默认 <Language> ,按照 <Workflows>输出结果。
最初のプロンプトはウェブページからテキストを取り出して翻訳し、画像には特別な処理を施し、テーブルのような形式は失われてしまいました。その後、GPTに直接HTMLをMarkdownに変換させるように最適化しました。これにより、特定のHTML表現であっても、相対的にフレンドリーなMarkdown形式に変換することができます。
具体的なC#コードの実装は次の通りです:
using Microsoft.Playwright;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using System.Text.RegularExpressions;
using System.Text;
namespace TranslateAgent
{
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
}
private void MainForm_Load(object sender, EventArgs e)
{
var lans = new List<string>
{
"日语","英语","法语","德语","韩语"
};
LanComBox.DataSource = lans;
}
(string title, string content) SplitArticle(string article)
{
if (!string.IsNullOrWhiteSpace(article))
{
var reader = new StringReader(article);
var title = reader.ReadLine();
var content = reader.ReadToEnd();
return (title, content);
}
throw new Exception("文章为空!");
}
async Task<bool> PublishArticleAsync(string translatorContent)
{
var (title, content) = SplitArticle(translatorContent);
var url = "";
this.Invoke(() =>
{
url = UrlTextBox.Text;
});
if (!string.IsNullOrWhiteSpace(url))
{
content += $"\r\n元のリンク:<span class="code-snippet__subst">{url}</span>";
}
using var playwright = await Playwright.CreateAsync();
await using var browser = await playwright.Chromium.LaunchAsync(new BrowserTypeLaunchOptions
{
Headless = false,
Args = ["--start-maximized"]
});
var page = await browser.NewPageAsync();
await page.GotoAsync("https://qiita.com/");
await page.ClickAsync("a[href='/login?callback_action=login_or_signup&redirect_to=%2F&realm=qiita']");
var userArr = File.ReadAllLines("C:/gpt/qiita_user.txt");
await page.FillAsync("#identity", userArr[0]);
await page.FillAsync("#password", userArr[1]);
await page.ClickAsync("input[name='commit']");
await page.GotoAsync("https://qiita.com/drafts/new");
await page.FillAsync("input[placeholder='記事タイトル']", title);
await page.FillAsync("input[placeholder='タグを入力してください。スペース区切りで5つまで入力できます。']", "C# .NET");
await page.FillAsync("div[role='textbox']", content);
MessageBox.Show("请确认发表内容,并且手动在弹出的内核浏览器中发布!请注意,点击确定后会自动关闭内核浏览器!!!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
return true;
}
async Task<(string Title, string Content)> GetArticleAsync(string url)
{
using var playwright = await Playwright.CreateAsync();
await using var browser = await playwright.Chromium.LaunchAsync(/*new BrowserTypeLaunchOptions { Headless = false }*/);
var page = await browser.NewPageAsync();
await page.GotoAsync(url);
var title = await page.Locator("#activity-name").InnerTextAsync();
var locator = page.Locator("#js_content");
var images = await locator.GetByRole(AriaRole.Img).ElementHandlesAsync();
var imageList = new List<string>();
foreach (var image in images)
{
var imgUrl = await image.GetAttributeAsync("data-src");
imageList.Add(imgUrl);
}
var html = await locator.InnerHTMLAsync();
var imgTagPattern = @"<img[^>]*>";
html = Regex.Replace(html, imgTagPattern, "[图片]");
await page.SetContentAsync("<div id='js_content'>" + html + "</div>");
var content = await page.Locator("#js_content").InnerTextAsync();
var reader = new StringReader(content);
var index = 0;
var contentBuilder = new StringBuilder();
while (true)
{
var line = await reader.ReadLineAsync();
if (!string.IsNullOrWhiteSpace(line.Trim()))
{
if (line.Trim() == "[图片]")
{
contentBuilder.AppendLine($"![alt 图片](<span class="code-snippet__subst">{imageList[index]}</span>)");
index++;
}
else
{
contentBuilder.AppendLine(line);
}
}
if (reader.Peek() <= 0)
{
break;
}
}
return (title, contentBuilder.ToString());
}
async Task<string> OpenAIChatSampleAsync(string title, string content)
{
var key = File.ReadAllText(@"C:\GPT\key.txt");
var chatModelId = "gpt-4-0125-preview";
OpenAIChatCompletionService chatCompletionService = new(chatModelId, key);
return await StartChatAsync(chatCompletionService, title, content);
}
async Task<string> StartChatAsync(IChatCompletionService chatGPT, string title, string content)
{
var lan = "日文";
this.Invoke(() =>
{
lan = LanComBox.Text;
});
var prompt = File.ReadAllText(Environment.CurrentDirectory + "/Prompt.md");
prompt = prompt.Replace("{{language}}", lan);
var chatHistory = new ChatHistory(prompt);
var userContent = $"标题:<span class="code-snippet__subst">{title}</span>\r\n内容:<span class="code-snippet__subst">{content}</span>";
chatHistory.AddUserMessage(userContent);
return await MessageStreamOutputAsync(chatGPT, chatHistory);
}
async Task<string> MessageStreamOutputAsync(IChatCompletionService chatGPT, ChatHistory chatHistory)
{
var list = chatGPT.GetStreamingChatMessageContentsAsync(chatHistory);
var fullMessage = string.Empty;
await foreach (var item in list)
{
if (item == null)
{
continue;
}
fullMessage += item.Content;
this.Invoke(() =>
{
TranslationTextBox.AppendText(item.Content);
});
}
return fullMessage;
}
private void TranButton_Click(object sender, EventArgs e)
{
try
{
var url = UrlTextBox.Text;
if (string.IsNullOrWhiteSpace(url))
{
MessageBox.Show("输入的url有误,请重新输入!", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
var pattern = @"^(https?:\/\/)?(?s).*$";
var regex = new Regex(pattern, RegexOptions.IgnoreCase);
bool isValid = regex.IsMatch(url);
if (!isValid)
{
MessageBox.Show("输入的url有误,请重新输入!", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
else
{
Task.Run(() =>
{
try
{
var (title, content) = GetArticleAsync(url).Result;
this.Invoke(() =>
{
OriginalTextBox.Lines = new string[] { title, content };
});
var translatorContent = OpenAIChatSampleAsync(title, content).Result;
var result = PublishArticleAsync(translatorContent).Result;
if (!result)
{
MessageBox.Show("翻译失败!", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
catch (Exception exc)
{
MessageBox.Show(exc.Message, "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
});
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private void LanComBox_SelectedIndexChanged(object sender, EventArgs e)
{
this.Text = "中文->" + LanComBox.Text;
}
private void ClearButton_Click(object sender, EventArgs e)
{
UrlTextBox.Clear();
OriginalTextBox.Clear();
TranslationTextBox.Clear();
}
}
}
(Translated by GPT)