使用 Wordcraft 打造 AI 写作助理

我们喜欢故事。讲故事和进行其他形式的创造性写作可能会极具挑战性和回报。但是,从空白页面构建自己的故事有时可能会让人望而生畏,甚至让人无所适从。人工智能 (AI) 生成模型是一个很好的工具,可帮助您超越空白并构建叙事内容。

本教程介绍如何扩展 Wordcraft,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 会为每个写作辅助操作(如开始新故事生成文本命令)使用一组示例为 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 的主菜单,然后选择 Start a New Story
  5. 更新新的故事提示或将其更改为所需内容,然后选择开启新故事

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

创建新的书写控件

Wordcraft 推出角色界面 Wordcraft 应用经过了扩展,因此您可以添加新的书写控件来帮助您,这类似于应用右侧“控件”标签页中的生成文本重写句子按钮。进行这些修改会花费更多精力,但可让您根据自己的工作流程和目标来塑造 Wordcraft 的功能。

以下示例修改为 Wordcraft 创建了一个新的字符控件。您可以使用新角色在故事中介绍新角色,并说明该角色的特征。此控件的基础与之前讨论的“start new story”控件等其他 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>;
    

构建界面

创建和注册内容生成示例后,即可为新控件创建界面。这一阶段的主要工作是创建新的 Operation 类,然后使用 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 构建生产应用(例如 Docs Agent),请查看 Google Cloud Vertex AI 服务以提高应用的可伸缩性和可靠性。