使用 Wordcraft 打造 AI 写作助理

我们喜欢讲故事。讲故事和进行其他形式的创意写作既具有挑战性,又能带来成就感。不过,从一张空白的页面开始构建自己的故事有时会让人感到畏惧,甚至不知所措。人工智能 (AI) 生成式模型是一款非常有用的工具,可帮助您克服空白页面,构建自己的叙事。

本教程介绍了如何扩展 Wordcraft,这是一个由 Google 人与 AI 研究团队打造的 AI 赋能的短篇故事创作工具。此 Web 应用使用 Gemini API 帮助您通过生成想法、撰写故事的部分内容以及修改内容以添加更多细节,一点一点地构建故事。您可以修改 Wordcraft,使其更贴近您自己的写作风格,并构建新的写作控件,以更好地支持您的工作流。

如需观看有关该项目及其扩展方式的视频简介,包括项目开发者的深入解析,请参阅 AI 写作助理 - 利用 Google AI 构建。否则,您可以按照以下说明开始扩展项目。

项目设置

以下说明将引导您完成 Wordcraft 项目的设置,以便进行开发和测试。您需要安装必需的软件、从代码库克隆项目、运行配置安装,并设置一些环境变量。完成这些步骤后,您可以通过运行项目来测试设置。

安装必备组件

Wordcraft 项目使用 Node 和 npm 来管理软件包并运行应用。以下安装说明适用于 Linux 主机。

如需安装所需的软件,请执行以下操作:

  • 按照适用于您的平台的安装说明安装 nodenpm

克隆并配置项目

下载项目代码,然后使用 npm 安装命令下载所需的依赖项并配置项目。您需要 git 源代码控制软件才能检索项目源代码。
如需下载和配置项目代码,请执行以下操作:

  1. 使用以下命令克隆 Git 代码库。
    git clone https://github.com/PAIR-code/wordcraft
    
  2. 前往 Wordcraft 项目根目录。
    cd wordcraft/
    
  3. 运行安装命令以下载依赖项并配置项目:
    npm install
    

设置环境变量

设置运行 Wordcraft 代码项目所需的环境变量,尤其是 Google Gemini API 密钥。以下安装说明适用于 Linux 主机。

如需设置环境变量,请执行以下操作:

  1. 获取 Google Gemini API 密钥并复制密钥字符串。
  2. 前往 Wordcraft 项目根目录。`
    cd wordcraft/
    
  3. 将 API 密钥设置为环境变量。在 Linux 主机上,请使用以下命令。
    touch .env
    echo "API_KEY="<YOUR_API_KEY>"" > .env
    

测试您的设置

现在,您应该能够在设备上运行 Wordcraft 来测试项目设置。此步骤并非强制性要求,但建议您执行此操作。

Wordcraft 开始屏幕

如需测试安装和设置,请执行以下操作:

  1. 前往 Wordcraft 项目根目录。
    cd wordcraft/
    
  2. 在开发模式下通过它运行项目:
    npm run dev
    
  3. 在网络浏览器中,打开 Wordcraft 界面。上一个命令的输出中会显示具体地址,例如:
    http://localhost:3000/
    

修改提示示例文本

Wordcraft 命令界面 Wordcraft 会针对每项写作辅助操作(例如 start new storygenerate text 命令)使用一组示例为 AI 生成式模型创建提示。这些示例可指导生成式模型为您的故事生成文本,通过修改操作的示例,您可以更改输出,使其遵循不同的写作模式或风格。这种方法可以让 Wordcraft 以您想要的方式进行写作,非常简单。

以下示例展示了对 Wordcraft 中的 new_story 示例所做的修改。此修改旨在指示 AI 生成式模型使用内心独白方法编写故事开头,并使用更适合推理小说的文风。通过撰写几篇此类故事开场白示例,您可以让生成式模型遵循一般模式,但为其他主题生成开场白。

如需在 Wordcraft 中修改新的短片故事示例,请执行以下操作:

  1. 打开 wordcraft/app/context/json/new_story.json 文件。
  2. 修改示例,同时保持 JSON 文件的整体结构。以下是使用内心独白风格修改的推理小说开头示例。
    [
      {
        "topic": "scientist disappears and their research into a new technology is gone",
        "target": "I got the call from the chief early Tuesday morning, before I'd even had a second sip of coffee. Terrible timing. Something about a researcher disappearing from the local university. Unusual for the research lab to ask for assistance, so I headed over to main lab building."
      },
      {
        "topic": "a young woman wakes up with no memory",
        "target": "An unfamiliar ceiling with harsh, white lights greeted my eyes as I opened them. I looked around. White walls, medical equipment, a hospital? Wait. Where am I? How did I get here?!"
      },
      {
        "topic": "old man tries to recall an important task as his memories gradually fade away",
        "target": "What was I supposed to do today? Feels like it was important. I stared into the kitchen cabinet full of mismatched mugs, mirroring my own confusion. What was it? Like someone is...in danger? A chill shot down my spine, but the details skittered off and hid in some dark corner of my head."
      },
      {
        "topic": "billionaire is found dead in a hotel room",
        "target": "People meet their end every day, some naturally, some unnaturally. After 17 years of working as a homicide detective in Seattle, I'd seen a lot more of the unnatural variety than most. Comes with the job, along with a hard-learned sense of what side of the line any given incident sat on. This...was murder."
      },
      {
        "topic": "retired covert operative gets dragged back into an old mission",
        "target": "Steam rose gently off the cup of Earl Grey sitting in front of me as I sat at the cafe, pedestrians and light traffic rolling by. The city was slowly waking up around me and my perfect Paris morning routine was shaping up nicely. Then I noticed that old familiar and unwelcome tingling on the back of my neck. I was being watched."
      }
    ]
  3. 将更改保存到 `new_story.json` 文件。

如需测试修改后的新故事操作,请执行以下操作:

  1. 前往 Wordcraft 项目根目录。
    cd wordcraft/
    
  2. 在开发模式下运行项目。如果该应用已在运行,您可能需要停止并重新启动该应用。
    npm run dev
    
  3. 在网络浏览器中,打开 Wordcraft 界面。上一个命令的输出中会显示具体地址,例如:
    http://localhost:3000/
    
  4. 前往 Wordcraft 的主菜单,然后选择开始写新故事
  5. 更新新故事提示或将其更改为您想要的内容,然后选择开始新故事

您可以使用此方法修改 Wordcraft 中的所有现有故事创作控件。尝试通过更新 wordcraft/app/context/json/ 目录中的示例来更改其他短片故事控件。

创建新的手写控件

Wordcraft 引入了字符界面 Wordcraft 应用旨在可扩展,因此您可以添加新的写作控件来提供帮助,类似于应用右侧“控件”标签页中的生成文本重写句子按钮。进行这些修改需要付出一些努力,但您可以根据自己的工作流程和目标塑造 Wordcraft 的功能。

以下示例修改会为 Wordcraft 创建一个新的字符控件。您可以使用它在故事中引入新角色,并描述该角色的属性。此控件的底层与其他 Wordcraft 控件(例如前面讨论的开始新故事控件)相同。您需要创建一个 JSON 文件,并在其中添加一些示例,说明您希望如何介绍角色。其余更改则添加了界面和 AI 提示管理功能。

创建示例

写几个示例,说明您希望生成式模型如何介绍角色。例如,您是想像讲述者一样描述它们,还是想通过主角的经历来介绍它们?以下示例使用后一种方法,从主角的视角介绍新角色。您可以使用新的 JSON 文件添加这些示例:

如需为新控件添加示例,请执行以下操作:

  1. 创建 wordcraft/app/context/json/new_character.json 文件。
  2. 在 JSON 文件中创建示例。在本例中,每个示例都有一个 character 说明字段(表示提示文本)和一个 target 字段(显示预期输出)。
    [
      {
        "character": "A character who is helpful and modest.",
        "target": "\"You lost, buddy?\" came a voice from behind me. Turning, I discovered a man dressed in a simple but presentable outfit. Small signs of age and loose threads hinted that these clothes, and the man himself, had seen better days."
      },
      {
        "character": "A character who is attractive and devious.",
        "target": "Stepping out of the alley a little too quickly, I collided with something solidly muscular and surprisingly delicately scented. \"Sorry.\" I managed, regaining my balance. \"Easy there, buddy, you're gonna hurt yourself,\" came the reply from a man with an almost feline grace, further reinforced by a stare that reminded me of a hunting cat assessing its potential prey."
      },
      {
        "character": "A character who is old and hesitant.",
        "target": "\"Excuse me. Do you know the way to the train station from here?\" I looked up from my phone to see a elderly woman in a threadbare coat, purse clutched with two hands in front of her. \"I-I'm supposed to meet my nephew there. Do... do you think you can help me?\""
      },
      {
        "character": "A character who is intelligent and aloof.",
        "target": "Bookish. That was my immediate reaction to this person I now saw in front of me. \"You're finally here. Did you read the notes I sent you?\" The voice sat squarely in between feminine and masculine intonation. \"No, of course you didn't.\" Dismissing my answer before I'd even formulated one. Annoyance immediately flushed through me."
      },
      {
        "character": "A character who is clumsy and energetic.",
        "target": "\"Whoa!\" was the only warning I had before someone slammed into my back, almost knocking me off my feet. \"I'm so sorry! WOOO! These skates are a RUSH!\" The apology came from a rather loud redhead wearing rollerblades, dark glasses and a very beefy-looking pair of headphones. That explained the volume of the apology."
      }
    ]
  3. 将更改保存到 new_character.json 文件。

创建示例后,修改 app/context/schema.tsindex.ts 文件,以反映此新字符控件的提示内容。

如需将示例添加到 schema.ts 文件,请执行以下操作:

  • 修改 wordcraft/app/context/schema.ts 文件以添加新的字符示例数据结构。
    export const newStorySchema = z.object({
      topic: z.string(),
      target: z.string(),
    });
    
    // add the following:
    export const newCharacterSchema = z.object({
      character: z.string(),
      target: z.string(),
    });

定义与这些新示例关联的操作类型。这种新类型有助于将问题示例与界面和问题构建代码相关联,您将在后续步骤中修改这些代码。

创建新操作类型

  • 修改 wordcraft/app/core/shared/types.ts 文件以添加新的字符操作类型。
    export const enum OperationType {
      ...
      NEW_CHARACTER = 'NEW_CHARACTER', // add to list of types
      ...
    }

如需在 index.ts 文件中注册示例,请执行以下操作:

  1. wordcraft/app/context/index.ts 文件中,导入新架构。
    import {
      continueSchema,
      ...
      newCharacterSchema // add new schema
    } from './schema';
  2. 将新的 JSON 文件作为 newCharacterJson 导入。
    import newCharacterJson from './json/new_character.json';
  3. 在应用上下文中注册新的字符示例内容。
    export class WordcraftContext {
      constructor() {
      ...
        this.registerExamples(
          OperationType.NEW_CHARACTER,
          newCharacterSchema,
          newCharacterJson
        );
      ...
    }
  4. 导出 NewCharacterExample 类型。
    export type NewCharacterExample = z.infer<typeof newCharacterSchema>;

构建界面

创建并注册内容生成示例后,您就可以为新控件创建界面了。此阶段的大部分工作是创建新的操作类,然后将该类注册到 Wordcraft 应用的主要代码中。

如需创建新操作,请执行以下操作:

  1. wordcraft/app/core/operations/ 目录中,使用现有操作类之一作为模板创建新的操作类。对于新字符控件,您可以复制 new_story_operation.ts 类并将其重命名为 new_character_operation.ts
  2. 为类命名,并通过定义至少一个 OperationSite 值来指定控件何时显示在界面中。
    export class NewCharacterOperation extends ChoiceOperation {
      static override isAvailable(operationSite: OperationSite) {
        return (
          operationSite === OperationSite.END_OF_SECTION ||
          operationSite === OperationSite.EMPTY_SECTION
        );
      }
  3. 为操作设置 id
      static override id = OperationType.NEW_CHARACTER;
  4. 更新 getrun 函数,以反映架构参数的值。此代码用于从界面获取提示文本,以便在 AI 提示中使用。
      private get character(): string {
        return NewCharacterOperation.controls.character.value;
      }
    
      async run() {
        const params = { character: this.character };
        const choices = await this.getModel().newCharacter(params);
    
        this.setChoices(choices);
      }
  5. 更新了界面文本和说明。
      static override getButtonLabel() {
        return 'introduce character';
      }
    
      static override getDescription() {
        return 'Introduce a new character at the cursor.';
      }
    
      static override controls = {
        character: new TextareaControl({
          prefix: 'prompt',
          description: 'A prompt to introduce a new character.',
          value: 'A new character.',
        }),
      };

如需在 Wordcraft 应用中注册新操作,请执行以下操作:

  1. wordcraft/app/core/operations/index.ts 文件中,为新操作添加导入。
    import {NewCharacterOperation} from './new_character_operation';
  2. 在同一 index.ts 文件中,为 NewCharacterOperation 类添加导出内容。
    export {
      ...
      NewCharacterOperation, // add this class
      ...
    };
  3. wordcraft/app/main.ts 文件中,注册新操作。
    const operationsService = wordcraftCore.getService(OperationsService);
    operationsService.registerOperations(
      ...
      Operations.NewCharacterOperation, // add new operation
      ...
    );

创建提示处理

作为创建新控件的最后一阶段,您需要创建用于处理为 AI 生成式模型生成提示和处理响应的代码。工作的主要部分是在 wordcraft/app/models/gemini/prompts/ 目录中构建一个提示处理程序,该处理程序从界面获取输入,并组装要传递给生成式模型的提示。

如需为提示参数定义接口,请执行以下操作:

  • wordcraft/app/core/shared/interfaces.ts 文件中,为提示参数的新操作添加接口。
    export interface NewCharacterPromptParams {
      character: string;
    }

如需为新操作定义提示处理程序,请执行以下操作:

  1. wordcraft/app/models/gemini/prompts/ 目录中,使用现有操作类之一作为模板创建新的提示处理程序类。对于新字符控件,您可以复制 new_story.ts 类并将其重命名为 new_character.ts,以此作为起点。
  2. 定义提示处理程序函数并导入 NewCharacterExample 类。
    import { NewCharacterPromptParams } from '@core/shared/interfaces';
    import { NewCharacterExample, WordcraftContext } from '../../../context';
    import { OperationType } from '@core/shared/types';
    import { GeminiModel } from '..';
    
    export function makePromptHandler(model: GeminiModel, context: WordcraftContext) {
      ...
    }
  3. 构建 generatePrompt() 函数,用于获取 AI 模型提示的界面输入。
      function generatePrompt(character: string) {
        const prefix = "Here's a character description: ";
        const suffix = "Introduce this character in the story.";
    
        if (character.trim() === '') {
          return 'Introduce a new character to the story.';
        } else {
          return `${prefix}${model.wrap(character)}\n${suffix}`;
        }
      }
  4. 创建一个 getPromptContext() 函数,用于将界面输入与示例回答组合并构建完整的提示。
      function getPromptContext() {
        const examples = context.getExampleData(
          OperationType.NEW_CHARACTER
        );
        let promptContext = model.getPromptPreamble();
        examples.forEach((example) => {
          const { character, target } = example;
          const prompt = generatePrompt(character);
          promptContext += `${prompt} ${model.wrap(target)}\n\n`;
        });
        return promptContext;
      }

如需集成新的字符提示处理脚本,请执行以下操作:

  1. wordcraft/app/models/gemini/index.ts 文件中,导入新字符操作的提示处理脚本。
    import {makePromptHandler as newCharacter} from './prompts/new_character';
  2. newCharacter 提示处理程序添加替换项定义。
      override newCharacter = this.makePromptHandler(newCharacter);

如需将提示参数注册到模型定义,请执行以下操作:

  1. wordcraft/app/models/model.ts 文件中,为新的 NewCharacterPromptParams 接口添加导入。
    import {
      ...
      NewCharacterPromptParams,
      ...
    } from '@core/shared/interfaces';
  2. 向模型类添加了 newCharacter 提示参数。
      async newCharacter(params: NewCharacterPromptParams): Promise<ModelResults> {
        throw new Error('Not yet implemented');
      }

测试新的写入控件

您的新控件应该已经可以通过 Wordcraft 界面进行测试了。请务必先检查代码是否存在编译错误,然后再继续操作。

如需测试新字符控制功能,请执行以下操作:

  1. 前往 Wordcraft 项目根目录。`
    cd wordcraft/
    
  2. 在开发模式下运行项目:
    npm run dev
    
  3. 在网络浏览器中,打开 Wordcraft 界面。上一个命令的输出中会显示具体地址,例如:
    http://localhost:3000/
    
  4. 在 Wordcraft 应用中,创建新故事或打开现有故事。
  5. 在故事编辑区域中,将光标移至故事的末尾。右侧的“控件”标签页中应会显示引入角色控件。
  6. 介绍角色字段中,输入新角色的简要说明,然后选择介绍角色按钮。

其他资源

如需详细了解 Wordcraft 项目,请参阅代码库。您可以在此拉取请求中查看本教程中所述的更改。

正式版应用

如果您打算面向大量受众群体部署自定义版 Wordcraft,请注意,您在使用 Google Gemini API 时可能会受到速率限制和其他使用限制。如果您考虑使用 Gemini API 构建生产应用(例如 Google 文档客服),请查看 Google Cloud Vertex AI 服务,以提高应用的可伸缩性和可靠性。