fix:基本实现相关功能
This commit is contained in:
13
src/main/services/ai/graph/readingReflectionGraph.ts
Normal file
13
src/main/services/ai/graph/readingReflectionGraph.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { END, START, StateGraph } from '@langchain/langgraph'
|
||||
import { ReadingReflectionState } from '../state/readingReflectionState'
|
||||
import { generateReadingReflectionContentNode } from '../nodes/readingReflectionContent'
|
||||
import { generateReadingReflectionSummaryNode } from '../nodes/readingReflectionSummary'
|
||||
|
||||
const workflow = new StateGraph(ReadingReflectionState)
|
||||
.addNode('generateReadingReflectionContent', generateReadingReflectionContentNode)
|
||||
.addNode('generateReadingReflectionSummary', generateReadingReflectionSummaryNode)
|
||||
.addEdge(START, 'generateReadingReflectionContent') // 开始 -> 生成正文
|
||||
.addEdge('generateReadingReflectionContent', 'generateReadingReflectionSummary') // 正文生成后 -> 生成摘要
|
||||
.addEdge('generateReadingReflectionSummary', END) // 摘要生成后 -> 结束
|
||||
|
||||
export const readingReflectionGraph = workflow.compile()
|
||||
24
src/main/services/ai/llmService.ts
Normal file
24
src/main/services/ai/llmService.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { ChatOpenAI } from '@langchain/openai'
|
||||
import Store from 'electron-store'
|
||||
import { CONFIG_STORE_KEY } from '@rpc/constants/store_key'
|
||||
|
||||
const StoreClass = (Store as any).default || Store
|
||||
const store = new StoreClass({ encryptionKey: CONFIG_STORE_KEY })
|
||||
|
||||
export const createChatModel = (type: 'reading' | 'summary', schema: any) => {
|
||||
const config = store.get(`chatModels.${type}`) as any
|
||||
console.log('chatModels', config)
|
||||
|
||||
if (!config || !config.apiKey) {
|
||||
throw new Error(`请先在设置中配置 ${type === 'reading' ? '心得' : '总结'} 模型的 API Key`)
|
||||
}
|
||||
|
||||
return new ChatOpenAI({
|
||||
apiKey: config.apiKey,
|
||||
configuration: {
|
||||
baseURL: config.baseURL || 'https://api.openai.com/v1'
|
||||
},
|
||||
modelName: config.modelName,
|
||||
temperature: config.temperature
|
||||
}).withStructuredOutput(schema)
|
||||
}
|
||||
55
src/main/services/ai/nodes/readingReflectionContent.ts
Normal file
55
src/main/services/ai/nodes/readingReflectionContent.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { PromptTemplate } from '@langchain/core/prompts'
|
||||
import { ReadingReflectionState } from '../state/readingReflectionState'
|
||||
import { REFLECTION_CONTENT_PROMPT } from '@main/services/ai/prompts/readingReflactionPrompts'
|
||||
import { z } from 'zod'
|
||||
import { AppDataSource } from '@main/db/data-source'
|
||||
import { ReadingReflectionTaskItem } from '@main/db/entities/ReadingReflectionTaskItem'
|
||||
import { createChatModel } from '@main/services/ai/llmService'
|
||||
|
||||
export const generateReadingReflectionContentNode = async (
|
||||
state: typeof ReadingReflectionState.State,
|
||||
config?: any
|
||||
) => {
|
||||
const taskId = config?.configurable?.thread_id
|
||||
const repo = AppDataSource.getRepository(ReadingReflectionTaskItem)
|
||||
const prompt = PromptTemplate.fromTemplate(REFLECTION_CONTENT_PROMPT)
|
||||
|
||||
const ReadingReflectionContentSchema = z.object({
|
||||
title: z.string().describe('标题'),
|
||||
content: z.string().describe('正文')
|
||||
})
|
||||
|
||||
const contentModel = createChatModel('reading', ReadingReflectionContentSchema)
|
||||
|
||||
const chain = prompt.pipe(contentModel)
|
||||
|
||||
// 针对不同职业的微调逻辑(可选)
|
||||
const occupationLabel =
|
||||
{
|
||||
student: '学生',
|
||||
teacher: '教师',
|
||||
professional: '职场人士',
|
||||
researcher: '科研工作者',
|
||||
other: '专业人士'
|
||||
}[state.occupation] || state.occupation
|
||||
|
||||
const res = (await chain.invoke({
|
||||
occupation: occupationLabel,
|
||||
bookName: state.bookName,
|
||||
author: state.author || '佚名',
|
||||
description: state.description,
|
||||
tone: state.tone || '温暖且理性',
|
||||
wordCount: state.wordCount || 1000
|
||||
})) as { title: string; content: string }
|
||||
|
||||
// 节点内部直接更新数据库状态
|
||||
if (taskId) {
|
||||
await repo.update(taskId, {
|
||||
status: 'WRITING',
|
||||
title: res.title,
|
||||
content: res.content,
|
||||
progress: 60
|
||||
})
|
||||
}
|
||||
return { title: res.title, content: res.content }
|
||||
}
|
||||
46
src/main/services/ai/nodes/readingReflectionSummary.ts
Normal file
46
src/main/services/ai/nodes/readingReflectionSummary.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { PromptTemplate } from '@langchain/core/prompts'
|
||||
import { ReadingReflectionState } from '../state/readingReflectionState'
|
||||
import { REFLECTION_SUMMARY_PROMPT } from '@main/services/ai/prompts/readingReflactionPrompts'
|
||||
import { z } from 'zod'
|
||||
import { createChatModel } from '@main/services/ai/llmService'
|
||||
|
||||
/**
|
||||
* 步骤 3:生成摘要和关键词
|
||||
* 该节点会接收上一个节点生成的 title 和 content
|
||||
*/
|
||||
export const generateReadingReflectionSummaryNode = async (
|
||||
state: typeof ReadingReflectionState.State
|
||||
) => {
|
||||
const prompt = PromptTemplate.fromTemplate(REFLECTION_SUMMARY_PROMPT)
|
||||
|
||||
// 定义输出的结构
|
||||
const summarySchema = z.object({
|
||||
summary: z.string().describe('100字以内的摘要'),
|
||||
keywords: z.array(z.string()).describe('3-5个关键词')
|
||||
})
|
||||
// const summaryModel = new ChatOpenAI({
|
||||
// apiKey: 'sk-172309b16482e6ad4264b1cd89f010d8',
|
||||
// configuration: {
|
||||
// baseURL: 'https://apis.iflow.cn/v1'
|
||||
// },
|
||||
// modelName: 'deepseek-v3.2',
|
||||
// temperature: 0.3
|
||||
// }).withStructuredOutput(summarySchema)
|
||||
const summaryModel = createChatModel('summary', summarySchema)
|
||||
|
||||
const chain = prompt.pipe(summaryModel)
|
||||
|
||||
const res = (await chain.invoke({
|
||||
title: state.title,
|
||||
content: state.content
|
||||
})) as {
|
||||
summary: string
|
||||
keywords: string[]
|
||||
}
|
||||
|
||||
// 返回的结果会自动合并到 ReflectionState 中
|
||||
return {
|
||||
summary: res.summary,
|
||||
keywords: res.keywords
|
||||
}
|
||||
}
|
||||
61
src/main/services/ai/prompts/readingReflactionPrompts.ts
Normal file
61
src/main/services/ai/prompts/readingReflactionPrompts.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
export const REFLECTION_CONTENT_PROMPT = `
|
||||
# Role
|
||||
你是一位在 {occupation} 领域拥有20年深厚资历的专家。你文笔{tone},擅长将理论书籍的核心观点与真实的行业场景深度结合。
|
||||
|
||||
# Goal
|
||||
请根据提供的书籍信息,以专业视角撰写一份高质量的读书心得。
|
||||
|
||||
# Constraints & Rules
|
||||
1. **禁止身份陈述 (No Self-Introduction)**:
|
||||
- **严禁**在开头或正文任何地方出现“作为一名...”、“我有...经验”、“从教/从业二十年”等自报家门的句子。
|
||||
- 你的专业性应通过文字深度、对行业痛点的理解和专业术语自然流露,而不是通过宣称身份。
|
||||
2. **场景化切入 (Direct Opening)**:
|
||||
- 开篇请直接从书籍的某个核心观点、一个具体的职场痛点、或一个生动的行业场景切入。
|
||||
- 例如:不要说“作为医生我经常看到...”,而要说“当走廊的灯光亮起,面对那些复杂的病例时,我常在想...”
|
||||
3. **职业化表达 (Professionalism)**:
|
||||
- 结合该职业特有的工作场景,将书籍内容转化为可操作的洞察。
|
||||
4. **输出限制**:
|
||||
- 必须严格按照 JSON 格式输出,不含任何 Markdown 代码块标签或解释。
|
||||
|
||||
# Context Data
|
||||
- 职业背景:{occupation}
|
||||
- 书籍名称:{bookName}
|
||||
- 作者:{author}
|
||||
- 书籍描述:{description}
|
||||
- 语气风格:{tone}
|
||||
- 目标字数:{wordCount}
|
||||
|
||||
# Output JSON Format
|
||||
{{
|
||||
"title": "此处填写读书心得标题",
|
||||
"content": "此处填写读书心得正文内容"
|
||||
}}
|
||||
`
|
||||
export const REFLECTION_SUMMARY_PROMPT = `
|
||||
# Role
|
||||
你是一位金牌图书营销编辑,擅长撰写能够瞬间抓住读者眼球的“内容提要”和“搜索关键词”。你对文字有着极高的敏感度,能够从冗长的感悟中精准钩织出书籍的核心灵魂。
|
||||
|
||||
# Goal
|
||||
基于提供的【读书心得正文】,提炼出一段极具感染力的摘要,并提取能精准覆盖内容核心的关键词。
|
||||
|
||||
# Constraints & Rules
|
||||
1. **摘要编写要求 (Summary)**:
|
||||
- **拒绝平铺直叙**:不要使用“本文介绍了...”这类陈旧开场,直接切入感悟的核心点。
|
||||
- **受众共鸣**:摘要应体现出书籍对读者的实际启发或职场/生活改变。
|
||||
- **精炼控制**:字数严格控制在 80-100 字之间,语言优美、有力。
|
||||
2. **关键词提取要求 (Keywords)**:
|
||||
- **多维覆盖**:必须包含 3-5 个词。
|
||||
- **权重分配**:1个书籍核心理念词,1-2个职业/场景词(如:幼儿教育、职业成长),1个情感/启发词。
|
||||
3. **输出限制**:
|
||||
- 必须且只能输出标准 JSON 格式,严禁任何正文外的解释说明。
|
||||
|
||||
# Context Data
|
||||
- 读书心得标题:{title}
|
||||
- 读书心得正文:{content}
|
||||
|
||||
# Output JSON Format
|
||||
{{
|
||||
"summary": "此处填写精炼且感人的摘要内容",
|
||||
"keywords": ["关键词1", "关键词2", "关键词3"]
|
||||
}}
|
||||
`
|
||||
36
src/main/services/ai/state/readingReflectionState.ts
Normal file
36
src/main/services/ai/state/readingReflectionState.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { Annotation } from '@langchain/langgraph'
|
||||
import { Occupation } from '@shared/types/reflections'
|
||||
|
||||
export const ReadingReflectionState = Annotation.Root({
|
||||
// 输入任务
|
||||
bookName: Annotation<string>(),
|
||||
author: Annotation<string | undefined>(),
|
||||
description: Annotation<string>(),
|
||||
occupation: Annotation<Occupation>(),
|
||||
tone: Annotation<string | undefined>(),
|
||||
wordCount: Annotation<number>(),
|
||||
|
||||
// 标题:使用简单的覆盖逻辑
|
||||
title: Annotation<string>({
|
||||
reducer: (_oldValue, newValue) => newValue,
|
||||
default: () => ''
|
||||
}),
|
||||
|
||||
// 内容
|
||||
content: Annotation<string>({
|
||||
reducer: (_oldValue, newValue) => newValue,
|
||||
default: () => ''
|
||||
}),
|
||||
|
||||
// 摘要
|
||||
summary: Annotation<string>({
|
||||
reducer: (_oldValue, newValue) => newValue,
|
||||
default: () => ''
|
||||
}),
|
||||
|
||||
// 关键词:数组通常使用追加或替换逻辑
|
||||
keywords: Annotation<string[]>({
|
||||
reducer: (_oldValue, newValue) => newValue,
|
||||
default: () => []
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user