用 Google Gemini 在 Pieces 框架中集成 AI
教程展示如何在生产力应用中集成 Google Gemini API,涵盖架构和集成实践。实用但属于系列教程,完整性有限。
教程展示如何在生产力应用中集成 Google Gemini API,涵盖架构和集成实践。实用但属于系列教程,完整性有限。
欢迎回来!在第 1 部分中,我们构建了一个完整的 PiecesOS 服务,它可以:
连接到 PiecesOS 并保持 WebSocket 连接
获取并缓存按天分组的工作流摘要
从注解中提取摘要内容
现在,所有基础设施都已经准备就绪。但问题是:拥有这些包含具体内容的原始摘要确实很酷,可它们并不完全……实用。我的意思是,我有这么多关于自己做过什么的文字,但我今天究竟完成了哪些事情?
这正是第 2 部分要解决的问题。我们将使用 Google 的 Gemini AI,把这些原始摘要转化为真正有价值的洞察。比如:
我接触了哪些项目?
我与谁进行了协作?
有哪些事情需要留到明天继续处理?
得益于第 1 部分,现在我们可以使用 SummaryWithContent 访问内容丰富的摘要数据:
// lib/models/daily_recap_models.dart
// Don't forget the imports!
import 'dart:convert';
import 'package:google_generative_ai/google_generative_ai.dart';
import '../models/daily_recap_models.dart';
SummaryWithContent {
id: "4f302bfd-f3c2-4f85-aa79-e7cb314e111d",
title: "Implemented WebSocket sync",
content: "# Project XYZ\nFixed critical authentication bug in OAuth token refresh.
Implemented real-time WebSocket synchronization with automatic
reconnection. Pair programmed with Bob on the WebSocket integration...",
timestamp: 2025-11-04 14:32:15
}
这很棒!但它仍然只是一段原始文本。我们真正想要的是结构化且可执行的洞察:
SUMMARY:
Successfully fixed critical authentication bug and implemented
real-time WebSocket synchronization for better data flow.
PROJECTS:
✅ Authentication Service [completed]
Fixed OAuth token refresh logic
🔄 WebSocket Integration [in_progress]
Implemented real-time sync with automatic reconnection
PEOPLE WORKED WITH:
• Alice (code review)
• Bob (pair programming)
REMINDERS:
⚠️ Test WebSocket with production load
⚠️ Update documentation for new auth flow
NOTES:
- Send a message in Google Chat about the progress!
看出区别了吗?一个是数据,另一个是信息。
Google 的 Gemini API 非常适合完成这项工作。它可以:
理解自然语言
提取结构化信息
返回 JSON(这正是我们所需要的!)
一次处理多条摘要
首先,在 pubspec.yaml 的 git: 依赖项下方添加 Gemini SDK:
dependencies:
google_generative_ai: ^0.4.6
你还需要一个 API 密钥。可以从 Google AI Studio 获取一个——在合理的使用量范围内,它是免费的!
我们来创建一个新服务:lib/services/daily_recap_service.dart。
class DailyRecapService {
final GenerativeModel _model;
DailyRecapService({required String apiKey})
: _model = GenerativeModel(
model: 'gemini-2.5-flash-lite', // Fast, efficient, and cost-effective!
apiKey: apiKey,
generationConfig: GenerationConfig(
temperature: 0.7, // Balanced creativity
topK: 40,
topP: 0.95,
maxOutputTokens: 2048,
responseMimeType: 'application/json',
),
);
Future<DailyRecapData> generateDailyRecap({
required DateTime date,
required List<SummaryWithContent> summaries,
}) async {
// We'll build this step by step!
}
}
gemini-2.5-flash-lite:速度更快,也更具成本效益
temperature: 0.7:既不会过于有创造性,也不会过于死板
现在,让我们逐步构建 generateDailyRecap 函数。
设计合适的提示词是一门艺术。以下是一些重要的技巧:
Analyze these summaries and tell me what I did today.
结果:始终尽量具体。AI 还不会读心……暂时不会!(不过 Pieces 会读心,但无所谓啦 😉)
Return JSON with: summary, projects, people.
结果:始终明确说明你希望 AI 返回什么,以便能够正确解析结果:
Extract and organize into this EXACT JSON format:
{
"summary": "Brief 1-2 sentence overview",
"people": ["Person1", "Person2"],
"projects": [
{
"name": "Project Name",
"description": "What was done",
"status": "in_progress" // or "completed" or "not_started"
}
],
"reminders": ["Reminder 1"],
"notes": ["Important insight"]
}
结果:AI 和人类很相似,示例是帮助它理解需求的最佳方式。
下面是我们最终要使用的提示词:
// lib/services/daily_recap_service.dart
// Add below DailyRecapService() and above Future<>
/// Build the prompt for Gemini
String _buildPrompt(DateTime date, String context) {
final dateStr =
'${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}';
return '''You are an AI assistant analyzing a developer's workstream summaries for the day: $dateStr.
Based on the following workstream summaries, extract and organize the information into a structured daily recap.
WORKSTREAM SUMMARIES:
$context
Your task is to analyze these summaries and create a comprehensive daily recap with the following information:
1. **summary** (string, 1-2 sentences): A brief overview of what was accomplished today. Focus on the main achievements and work done.
2. **people** (array of strings): List of people mentioned or collaborated with. Look for names, @mentions, or collaboration indicators. Can be empty if no one is mentioned.
3. **projects** (array of objects): Projects worked on today. Each project should have:
- **name** (string): Project or feature name
- **description** (string): Brief description of what was done
- **status** (string): One of: "completed", "in_progress", or "not_started"
4. **reminders** (array of strings): Action items, TODOs, or things to remember for later. Look for phrases like "need to", "should", "TODO", "remember to", etc. Can be empty.
5. **notes** (array of strings): Important observations, learnings, or technical notes from the day. Look for insights, discoveries, or important information. Can be empty.
IMPORTANT GUIDELINES:
- Be concise but informative
- Extract actual information from the summaries, don't make things up
- If a category has no relevant information, use an empty array [] or empty string ""
- For project status: use "completed" if the work is done, "in_progress" if actively working on it, "not_started" if mentioned but not begun
- People names should be just the name (e.g., "Alice", "Bob")
- Keep descriptions clear and specific
Return ONLY valid JSON in this exact format:
{
"summary": "Brief 1-2 sentence overview",
"people": ["Person1", "Person2"],
"projects": [
{
"name": "Project Name",
"description": "What was done",
"status": "in_progress"
}
],
"reminders": ["Reminder 1", "Reminder 2"],
"notes": ["Note 1", "Note 2"]
}
''';
}
明确的角色:“You are an AI assistant...”
具体的格式:精确的 JSON 结构
示例:展示我们想要的结果
约束条件:“Don't make things up”“Empty arrays if no data”
枚举值:明确列出状态选项
_buildPrompt 和 _buildSummariesContext(接下来会介绍)这两个辅助方法驱动着我们的分析。但它们在实际应用程序中处于什么位置呢?
现在我们已经获得了摘要的实际内容,可以构建一个上下文丰富的提示词:
// lib/services/daily_recap_service.dart
// Add below _buildPrompt() and above generateDailyRecap()
String _buildSummariesContext(List<SummaryWithContent> summaries) {
final buffer = StringBuffer();
for (int i = 0; i < summaries.length; i++) {
final summary = summaries[i];
buffer.writeln('Summary ${i + 1}:');
buffer.writeln(' ID: ${summary.id}');
buffer.writeln(' Title: ${summary.title}');
buffer.writeln(
' Time: ${summary.timestamp.hour.toString().padLeft(2, '0')}:${summary.timestamp.minute.toString().padLeft(2, '0')}',
);
buffer.writeln(' Content: ${summary.content}');
buffer.writeln();
}
return buffer.toString();
}
这段代码会使用结构化标签和元数据来格式化每条摘要,为 Gemini 提供丰富得多的上下文。
接下来,我们添加一个空的工厂方法,用于创建空的 DailyRecapData:
// lib/services/daily_recap_service.dart
// Add below _buildPrompt() and above generateDailyRecap()
factory DailyRecapData.empty(DateTime date) {
return DailyRecapData(
date: date,
summary: '',
people: [],
projects: [],
reminders: [],
notes: [],
);
}
Gemini 会返回 JSON(因为我们设置了 responseMimeType),所以解析过程非常直接:
// lib/services/daily_recap_service.dart
// Replace the comment in generateDailyRecap() with this:
```dart
try {
final response = await _model.generateContent([Content.text(prompt)]);
print("Gemini response received. ${response.text}"); // 调试输出
final rawText = response.text ?? '{}';
// 从 markdown 代码块中提取 JSON
final jsonText = _extractJsonFromMarkdown(rawText);
final data = jsonDecode(jsonText) as Map<String, dynamic>;
return DailyRecapData.fromJson(date, data); // ... 生成总结
} catch (e) {
print('Error generating recap: $e');
return DailyRecapData.empty(date); // 安全降级
}
现在我们已经看到了所有部分,下面是它们如何在实际的 generateDailyRecap 函数中整合在一起的(你的应该是这样的 😉):
Future<DailyRecapData> generateDailyRecap({
required DateTime date,
required List<SummaryWithContent> summaries,
}) async {
// 步骤1:处理边界情况 - 没有摘要
if (summaries.isEmpty) {
return DailyRecapData.empty(date);
}
// 步骤2:使用辅助方法从摘要构建上下文
final context = _buildSummariesContext(summaries);
// 步骤3:使用提示词生成器构建提示词
final prompt = _buildPrompt(date, context);
try {
// 步骤4:发送给 Gemini 并获取响应
final response = await _model.generateContent([Content.text(prompt)]);
print("Gemini response received. ${response.text}");
// 步骤5:解析 JSON 响应
final jsonText = response.text ?? '{}';
final data = jsonDecode(jsonText) as Map<String, dynamic>;
// 步骤6:转换为数据模型并返回
return DailyRecapData.fromJson(date, data);
} catch (e) {
print('Error generating daily recap: $e');
rethrow; // 让 UI 处理错误
}
}
检查摘要是否为空
从所有摘要构建上下文字符串
创建带指令的提示词
解析 JSON 响应
返回结构化数据(或抛出错误)
我创建了干净的数据类来处理数据:
// lib/models/daily_recap_models.dart
// 在 SummaryWithContent 类之前、ProjectStatus {} 之后添加
class ProjectData {
final String name;
final String description;
final ProjectStatus status; // enum: completed, inProgress, notStarted
ProjectData({
required this.name,
required this.description,
required this.status,
});
factory ProjectData.fromJson(Map<String, dynamic> json) {
return ProjectData(
name: json['name'] as String,
description: json['description'] as String,
status: _statusFromString(json['status'] as String),
);
}
static ProjectStatus _statusFromString(String status) {
switch (status) {
case 'completed':
return ProjectStatus.completed;
case 'in_progress':
return ProjectStatus.inProgress;
case 'not_started':
return ProjectStatus.notStarted;
default:
return ProjectStatus.notStarted;
}
}
}
class DailyRecapData {
final DateTime date;
final String summary;
final List<String> people;
final List<ProjectData> projects;
final List<String> reminders;
final List<String> notes;
DailyRecapData({
required this.date,
required this.summary,
required this.people,
required this.projects,
required this.reminders,
required this.notes,
});
factory DailyRecapData.fromJson(DateTime date, Map<String, dynamic> json) {
return DailyRecapData(
date: date,
summary: json['summary'] as String? ?? '',
people: (json['people'] as List<dynamic>?)
?.map((e) => e as String)
.toList() ??
[],
projects: (json['projects'] as List<dynamic>?)
?.map((e) => ProjectData.fromJson(e as Map<String, dynamic>))
.toList() ??
[],
reminders: (json['reminders'] as List<dynamic>?)
?.map((e) => e as String)
.toList() ??
[],
notes: (json['notes'] as List<dynamic>?)
?.map((e) => e as String)
.toList() ??
[],
);
}
}
这给了我们类型安全性,并使其易于在以后处理数据。
✅ 理解我正在做"Pieces 操作系统集成"
✅ 正确识别其为"已完成"
✅ 从我的工作中提取了实际的提醒
✅ 提取了我发现的技术笔记
✅ 写了一个关于这一天的连贯总结
而它仅通过时间戳和标题就做到了所有这些!
以下是 Gemini 从我的工作流摘要生成的实际 JSON 响应:
{
"summary": "Today's work focused on enhancing user experience for video content, developing a tag generator, testing Flutter capabilities, and reviewing code and infrastructure. Significant progress was made on persona generation for AI training data.",
"people": [],
"projects": [
{
"name": "Video Analytics & User Experience",
"description": "Analyzed YouTube video analytics for 'Pieces' content and discussed strategies for improving new user experience by leveraging context and memory,
considering phased UI exposure and temporary access keys.",
"status": "in_progress"
},
{
"name": "Tag Generator",
"description": "Generated a Python script for thematic tagging.",
"status": "completed"
},
{
"name": "Flutter macOS Dynamic Library Loading",
"description": "Demonstrated Flutter's macOS dynamic library loading and confirmed clipboard monitoring functionality and its integration with long-term memory.",
"status": "in_progress"
},
{
"name": "AI Persona Generation",
"description": "Discussed UI for AI persona generation and initiated content compilation for a partner. Presented the 'persona-query-tag-dataset-gen' project, det
ailing the creation of realistic user personas for the Pieces AI assistant, showcasing comprehensive attributes and providing an example of 'Anja Vestergaard'.",
"status": "in_progress"
},
{
"name": "ML Training & Django API",
"description": "Addressed an `UnboundLocalError` in ML training and validated nested task management for a Django API.",
"status": "completed"
},
{
"name": "Infrastructure Upgrades & Containerization",
"description": "Reviewed infrastructure upgrades, containerization strategies, cost optimizations, and bug fixes across multiple services, including timezone and
user invitation flows.",
"status": "in_progress"
}
],
"reminders": [],
"notes": [
"Leveraging context and memory for new user experience improvements.",
"Clipboard monitoring functionality confirmed and integrated with long-term memory.",
"Objective for persona generation project is to generate authentic training data for the AI assistant."
]
}
供你参考,下面是完整的 lib/services/daily_recap_service.dart 文件,包含我们构建的所有内容:
使用 Gemini 配置的服务初始化
组织一切的 generateDailyRecap 函数
用于格式化摘要的 _buildSummariesContext 辅助方法
用于构建 AI 提示词的 _buildPrompt 辅助方法
class DailyRecapService {
final GenerativeModel _model;
late final Box<DailyRecapData> _cacheBox;
DailyRecapService({required String apiKey})
: _model = GenerativeModel(
model: 'gemini-2.5-flash-lite',
apiKey: apiKey,
generationConfig: GenerationConfig(
temperature: 0.7,
topK: 40,
topP: 0.95,
maxOutputTokens: 2048,
responseMimeType: 'application/json',
),
);
/// 从工作流摘要及其内容生成日常总结
/// 设置 forceRegenerate 为 true 来绕过缓存
Future<DailyRecapData> generateDailyRecap({
required DateTime date,
required List<SummaryWithContent> summaries,
}) async {
// 先检查缓存(除非强制重新生成)
if (summaries.isEmpty) {
return DailyRecapData.empty(date);
}
// 从摘要构建上下文
final context = _buildSummariesContext(summaries);
// 构建提示词
final prompt = _buildPrompt(date, context);
try {
final response = await _model.generateContent([Content.text(prompt)]);
// ignore: avoid_print
print("Gemini response received. ${response.text}");
final jsonText = response.text ?? '{}';
// 解析 JSON 响应
final data = jsonDecode(jsonText) as Map<String, dynamic>;
final recap = DailyRecapData.fromJson(date, data);
return recap;
} catch (e) {
// ignore: avoid_print
print('Error generating daily recap: $e');
rethrow; // 抛出错误以便 UI 处理
}
}
/// 从包含内容的摘要构建上下文字符串
String _buildSummariesContext(List<SummaryWithContent> summaries) {
final buffer = StringBuffer();
for (int i = 0; i < summaries.length; i++) {
final summary = summaries[i];
buffer.writeln('Summary ${i + 1}:');
buffer.writeln(' ID: ${summary.id}');
buffer.writeln(' Title: ${summary.title}');
buffer.writeln(
' Time: ${summary.timestamp.hour.toString().padLeft(2, '0')}:${summary.timestamp.minute.toString().padLeft(2, '0')}',
);
buffer.writeln(' Content: ${summary.content}');
buffer.writeln();
}
return buffer.toString();
/// 从 markdown 代码块中提取 JSON
String _extractJsonFromMarkdown(String text) {
// 移除 markdown 代码块格式
String cleaned = text.trim();
// 移除开头的 ```json 或 ```
if (cleaned.startsWith('```')) {
cleaned = cleaned.replaceFirst(RegExp(r'^```(?:json)?\s*'), '');
}
// 移除结尾的 ```
if (cleaned.endsWith('```')) {
cleaned = cleaned.replaceFirst(RegExp(r'\s*```$'), '');
}
return cleaned.trim();
}
/// 为 Gemini 构建提示词
String _buildPrompt(DateTime date, String context) {
final dateStr =
'${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}';
return '''You are an AI assistant analyzing a developer's workstream summaries for the day: $dateStr.
Based on the following workstream summaries, extract and organize the information into a structured daily recap.
WORKSTREAM SUMMARIES:
$context
Your task is to analyze these summaries and create a comprehensive daily recap with the following information:
1. **summary** (string, 1-2 sentences): A brief overview of what was accomplished today. Focus on the main achievements and work done.
2. **people** (array of strings): List of people mentioned or collaborated with. Look for names, @mentions, or collaboration indicators. Can be empty if no one is mentioned.
3. **projects** (array of objects): Projects worked on today. Each project should have:
- **name** (string): Project or feature name
- **description** (string): Brief description of what was done
- **status** (string): One of: "completed", "in_progress", or "not_started"
4. **reminders** (array of strings): Action items, TODOs, or things to remember for l
参考 GitHub 查看完整项目。