Skip to content

Commit b942df8

Browse files
committed
feat(ai): added support for ollama
1 parent af9763f commit b942df8

File tree

7 files changed

+389
-232
lines changed

7 files changed

+389
-232
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
"dependencies": {
2626
"chalk": "5.3.0",
2727
"https-proxy-agent": "^7.0.5",
28+
"ollama": "^0.5.14",
2829
"openai": "^4.52.7",
2930
"ora": "^8.0.1",
3031
"puppeteer": "22.13.1",

packages/ai/context.ts

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
1-
const BACKGROUND = `你现在是一名爬虫专家和前端专家, x-crawl 用户会将一段 HTML 片段发送给你, 你需要将该段 HTML 进行解析, 然后分析 coder 用户所提供的信息帮助他找出所需的内容, 并返回如下所说的 JSON 对象格式。
1+
const BACKGROUND = `你现在是一名爬虫专家和前端专家, html 会将一段 HTML 片段发送给你, 你需要将该段 HTML 进行解析, 然后分析 me 所提供的信息帮助他找出所需的内容, 并返回如下所说的 JSON 对象格式。
22
3-
x-crawl 用户: HTML string
4-
我们需要遍历 HTML 片段并检查每个元素的内容, 从而确定哪些项是 coder 所需的。然后, 我们可以根据这些元素在DOM树中的位置来结果。
3+
html: HTML string
4+
我们需要遍历 HTML 片段并检查每个元素的内容, 从而确定哪些项是 me 所需的。然后, 我们可以根据这些元素在DOM树中的位置来结果。
55
`
66

77
export const PARSE_ELEMENTS_CONTEXT = `
88
${BACKGROUND}
99
10-
coder 用户: { message: string }
10+
me: { message: string }
1111
发来了一个 JavaScript 对象转换为 JSON 字符串的值。
1212
- message:
1313
- 类型: string,
14-
- 作用: 用户的需求
14+
- 作用: 的需求
1515
1616
返回值: { elements: {key: value}[], type: string }
1717
需要返回这样一个 JSON 对象格式。
@@ -25,15 +25,15 @@ coder 用户: { message: string }
2525
2626
你需要根据HTML结构选择合适的属性:
2727
1.解析和理解message
28-
- 解析JSON字符串: 需要解析coder用户发送的JSON字符串,从中提取出message字段。
29-
- 自然语言处理:利用自然语言处理技术对message字段进行分析,以识别出用户希望选择的元素类型(如div、span等)、特征(如类名、ID、属性等),以及其他可能的筛选条件或要求。
30-
- 需求转换:基于自然语言处理的结果,将用户的需求转换为一组可用于查询DOM树的CSS选择器或XPath表达式
28+
- 解析JSON字符串: 需要解析me发送的JSON字符串,从中提取出message字段。
29+
- 自然语言处理:利用自然语言处理技术对message字段进行分析,以识别出希望选择的元素类型(如div、span等)、特征(如类名、ID、属性等),以及其他可能的筛选条件或要求。
30+
- 需求转换:基于自然语言处理的结果,将的需求转换为一组可用于查询DOM树的CSS选择器或XPath表达式
3131
2.解析HTML片段
32-
- HTML解析:AI使用HTML解析器将coder用户提供的HTML片段转换为一个DOM树结构。DOM树是一个树形数据结构,它表示了HTML文档的结构,使得AI能够方便地遍历和查询元素。
32+
- HTML解析:AI使用HTML解析器将me提供的HTML片段转换为一个DOM树结构。DOM树是一个树形数据结构,它表示了HTML文档的结构,使得AI能够方便地遍历和查询元素。
3333
- 构建DOM树:解析器将HTML片段中的标签、属性和文本转换为DOM节点,并建立起它们之间的父子关系,形成一个完整的DOM树。
3434
3.选择元素并提取属性
3535
- 元素选择:AI使用在步骤一中生成的CSS选择器或XPath表达式在DOM树中进行元素选择。这将返回一个或多个匹配的元素节点。
36-
- 属性提取:对于每个匹配的元素节点,AI将提取用户指定的属性。这可以通过访问DOM节点的属性集合来完成。AI需要确保提取的属性名称与用户在message字段中指定的相匹配
36+
- 属性提取:对于每个匹配的元素节点,AI将提取指定的属性。这可以通过访问DOM节点的属性集合来完成。AI需要确保提取的属性名称与在message字段中指定的相匹配
3737
- 构建属性对象:对于每个元素的每个属性,AI将创建一个包含key(属性名)和value(属性值)的对象,并将这些对象添加到elements数组中。
3838
4.确定并返回type字段
3939
- 元素计数:AI将统计找到的匹配元素的数量。这个数量将决定返回的type字段的值。
@@ -48,11 +48,11 @@ coder 用户: { message: string }
4848
export const GET_ELEMENT_SELECTORS_CONTEXT = `
4949
${BACKGROUND}
5050
51-
coder 用户: { message: string, pathMode: string }
51+
me: { message: string, pathMode: string }
5252
发来了一个 JavaScript 对象转换为 JSON 字符串的值。
5353
- message:
5454
- 类型: string,
55-
- 作用: 用户的需求
55+
- 作用: me 的需求
5656
- pathMode:
5757
- 类型: string, default 或者 strict
5858
- 作用: default 则可以不从 HTML 片段的根部开始的 selectors , 为 strict 则说明必需从 HTML 片段的根部开始的 selectors 。
@@ -69,7 +69,7 @@ coder 用户: { message: string, pathMode: string }
6969
7070
你需要根据HTML结构选择合适的选择器:
7171
1.解析和理解message与pathMode
72-
- 解析message: 从coder用户发送的JSON字符串中提取message字段, 并进行自然语言处理, 识别出用户希望选择的元素类型、特征或其他要求。
72+
- 解析message: 从 me 发送的JSON字符串中提取message字段, 并进行自然语言处理, 识别出希望选择的元素类型、特征或其他要求。
7373
- 解析pathMode: 提取pathMode字段的值, 并判断是default还是strict。这将决定选择器是否必须从HTML片段的根部开始。
7474
2.解析HTML片段
7575
- 构建DOM树: 将提供的HTML片段解析为DOM树, 以便你进行元素选择和遍历。
@@ -103,19 +103,19 @@ coder 用户: { message: string, pathMode: string }
103103
104104
## 示例(找不到的情况)
105105
106-
x-crawl 用户: "
106+
html: "
107107
<div class="list-item">安卓充电线</div>
108108
<div class="list-item">苹果充电线</div>
109109
"
110110
111-
coder 用户:{ "message": "获取 TYPE-C 充电线。", "isFullPath": false }
111+
me: { "message": "获取 TYPE-C 充电线。", "isFullPath": false }
112112
113113
返回值: { "selectors": "", "type": "none"}
114114
115115
分析: 这里没有 TYPE-C 类型的充电线, 只能将 isExist 设为 false , selectors 设为 ""。
116116
`
117-
export const HELP_CONTEXT = `我现在有一个爬虫相关的问题需要请教你。作为爬虫专家和前端专家,需要能帮我解答一下, 只需回答 coder 用户的问题
117+
export const HELP_CONTEXT = `我现在有一个爬虫相关的问题需要请教你。作为爬虫专家和前端专家,需要能帮我解答一下, 只需回答me的问题
118118
119119
x-crawl 是一个灵活的 Node.js AI 辅助爬虫库。强大的 AI 辅助功能,使爬虫工作变得更加高效、智能和便捷。
120-
x-crawl GitHub: https://github.com/coder-hxl/x-craw
120+
x-crawl GitHub: https://github.com/me-hxl/x-craw
121121
`

packages/ai/index.ts

Lines changed: 2 additions & 214 deletions
Original file line numberDiff line numberDiff line change
@@ -1,214 +1,2 @@
1-
import OpenAI, { ClientOptions } from 'openai'
2-
import ora from 'ora'
3-
4-
import {
5-
PARSE_ELEMENTS_CONTEXT,
6-
GET_ELEMENT_SELECTORS_CONTEXT,
7-
HELP_CONTEXT
8-
} from './context'
9-
import { isObject, logStart, logSuccess } from '../shared'
10-
11-
type OpenAIChatModel =
12-
| 'gpt-4o'
13-
| 'gpt-4o-2024-05-13'
14-
| 'gpt-4-turbo'
15-
| 'gpt-4-turbo-2024-04-09'
16-
| 'gpt-4-0125-preview'
17-
| 'gpt-4-turbo-preview'
18-
| 'gpt-4-1106-preview'
19-
| 'gpt-4-vision-preview'
20-
| 'gpt-4'
21-
| 'gpt-4-0314'
22-
| 'gpt-4-0613'
23-
| 'gpt-4-32k'
24-
| 'gpt-4-32k-0314'
25-
| 'gpt-4-32k-0613'
26-
| 'gpt-3.5-turbo'
27-
| 'gpt-3.5-turbo-16k'
28-
| 'gpt-3.5-turbo-0301'
29-
| 'gpt-3.5-turbo-0613'
30-
| 'gpt-3.5-turbo-1106'
31-
| 'gpt-3.5-turbo-0125'
32-
| 'gpt-3.5-turbo-16k-0613'
33-
34-
interface CreateCrawlOpenAIConfig {
35-
defaultModel?: {
36-
chatModel: (string & {}) | OpenAIChatModel
37-
}
38-
clientOptions?: ClientOptions
39-
}
40-
41-
interface CrawlOpenAICommonAPIOtherOption {
42-
model?: (string & {}) | OpenAIChatModel
43-
}
44-
45-
interface CrawlOpenAIRunChatOption {
46-
model: (string & {}) | OpenAIChatModel | undefined
47-
context: string
48-
HTMLContent: string
49-
userContent: string
50-
responseFormatType: 'text' | 'json_object'
51-
}
52-
53-
interface CrawlOpenAIParseElementsContentOptions {
54-
message: string
55-
}
56-
57-
interface CrawlOpenAIGetElementSelectorsContentOptions {
58-
message: string
59-
pathMode: 'default' | 'strict'
60-
}
61-
62-
interface CrawlOpenAIGetElementSelectorsResult {
63-
selectors: string
64-
type: 'single' | 'multiple' | 'none'
65-
}
66-
67-
interface CrawlOpenAIParseElementsResult<T extends Record<string, string>> {
68-
elements: T[]
69-
type: 'single' | 'multiple' | 'none'
70-
}
71-
72-
interface CrawlOpenAIApp {
73-
parseElements<T extends Record<string, string>>(
74-
HTML: string,
75-
content: string | CrawlOpenAIParseElementsContentOptions,
76-
option?: CrawlOpenAICommonAPIOtherOption
77-
): Promise<CrawlOpenAIParseElementsResult<T>>
78-
79-
getElementSelectors(
80-
HTML: string,
81-
content: string | CrawlOpenAIGetElementSelectorsContentOptions,
82-
option?: CrawlOpenAICommonAPIOtherOption
83-
): Promise<CrawlOpenAIGetElementSelectorsResult>
84-
85-
help(
86-
content: string,
87-
option?: CrawlOpenAICommonAPIOtherOption
88-
): Promise<string>
89-
90-
custom(): OpenAI
91-
}
92-
93-
export function createCrawlOpenAI(
94-
config: CreateCrawlOpenAIConfig = {}
95-
): CrawlOpenAIApp {
96-
const { defaultModel, clientOptions } = config
97-
98-
const openai = new OpenAI(clientOptions)
99-
const chatDefaultModel: (string & {}) | OpenAIChatModel =
100-
defaultModel?.chatModel ?? 'gpt-3.5-turbo'
101-
102-
async function runChat<T>(option: CrawlOpenAIRunChatOption): Promise<T> {
103-
const {
104-
model = chatDefaultModel,
105-
context,
106-
HTMLContent,
107-
userContent,
108-
responseFormatType
109-
} = option
110-
111-
const spinner = ora(
112-
logStart(`AI is answering your question, please wait a moment`)
113-
).start()
114-
const completion = await openai.chat.completions.create({
115-
model,
116-
messages: [
117-
{ role: 'system', content: context },
118-
{ role: 'user', name: 'x-crawl', content: HTMLContent },
119-
{ role: 'user', name: 'coder', content: userContent }
120-
],
121-
response_format: { type: responseFormatType },
122-
temperature: 0.1
123-
})
124-
spinner.succeed(logSuccess(`AI has completed your question`))
125-
126-
const content = completion.choices[0].message.content
127-
const result =
128-
responseFormatType === 'json_object' ? JSON.parse(content!) : content
129-
130-
return result
131-
}
132-
133-
const app: CrawlOpenAIApp = {
134-
async parseElements<T extends Record<string, string>>(
135-
HTML: string,
136-
content: string | CrawlOpenAIParseElementsContentOptions,
137-
option: CrawlOpenAICommonAPIOtherOption = {}
138-
): Promise<CrawlOpenAIParseElementsResult<T>> {
139-
const { model } = option
140-
141-
let coderContent: string = ''
142-
if (isObject(content)) {
143-
coderContent = JSON.stringify(content)
144-
} else {
145-
const obj: CrawlOpenAIParseElementsContentOptions = {
146-
message: content
147-
}
148-
coderContent = JSON.stringify(obj)
149-
}
150-
151-
const result = await runChat<CrawlOpenAIParseElementsResult<T>>({
152-
model,
153-
context: PARSE_ELEMENTS_CONTEXT,
154-
HTMLContent: HTML,
155-
userContent: coderContent,
156-
responseFormatType: 'json_object'
157-
})
158-
159-
return result
160-
},
161-
162-
async getElementSelectors(
163-
HTML: string,
164-
content: string | CrawlOpenAIGetElementSelectorsContentOptions,
165-
option: CrawlOpenAICommonAPIOtherOption = {}
166-
): Promise<CrawlOpenAIGetElementSelectorsResult> {
167-
const { model } = option
168-
169-
let coderContent: string = ''
170-
if (isObject(content)) {
171-
coderContent = JSON.stringify(content)
172-
} else {
173-
const obj: CrawlOpenAIGetElementSelectorsContentOptions = {
174-
message: content,
175-
pathMode: 'default'
176-
}
177-
coderContent = JSON.stringify(obj)
178-
}
179-
180-
const result = await runChat<CrawlOpenAIGetElementSelectorsResult>({
181-
model,
182-
context: GET_ELEMENT_SELECTORS_CONTEXT,
183-
HTMLContent: HTML,
184-
userContent: coderContent,
185-
responseFormatType: 'json_object'
186-
})
187-
188-
return result
189-
},
190-
191-
async help(
192-
content: string,
193-
option: CrawlOpenAICommonAPIOtherOption = {}
194-
): Promise<string> {
195-
const { model } = option
196-
197-
const result = await runChat<string>({
198-
model,
199-
context: HELP_CONTEXT,
200-
HTMLContent: '',
201-
userContent: content,
202-
responseFormatType: 'text'
203-
})
204-
205-
return result
206-
},
207-
208-
custom() {
209-
return openai
210-
}
211-
}
212-
213-
return app
214-
}
1+
export * from './openai'
2+
export * from './ollama'

0 commit comments

Comments
 (0)