feat: 本项目基本完成

新增 rerankURL、vectorDBURL 配置,以及 rerankModel、retriveTopK、
rerankTopK 和 memory 等参数,以支持更完整的检索和重排序功能。
This commit is contained in:
2025-10-26 02:05:11 +08:00
parent 2199e8b015
commit c2efd3fdf4
7 changed files with 421 additions and 282 deletions

View File

@@ -1,7 +1,13 @@
{ {
"queryURL":"https://api.siliconflow.cn/v1/chat/completions", "queryURL":"https://api.siliconflow.cn/v1/chat/completions",
"embeddingURL":"https://api.siliconflow.cn/v1/embeddings", "embeddingURL":"https://api.siliconflow.cn/v1/embeddings",
"rerankURL":"https://api.siliconflow.cn/v1/rerank",
"vectorDBURL":"http://localhost:8000",
"key":"sk-xsoegkpdvqlbsoodrnaygqycdvhplkyowivkzlszqfytpvti", "key":"sk-xsoegkpdvqlbsoodrnaygqycdvhplkyowivkzlszqfytpvti",
"queryModel":"Qwen/Qwen3-8B", "queryModel":"Qwen/Qwen3-8B",
"embeddingModel":"Qwen/Qwen3-Embedding-0.6B" "embeddingModel":"Qwen/Qwen3-Embedding-0.6B",
"rerankModel":"Qwen/Qwen3-Reranker-0.6B",
"retriveTopK":20,
"rerankTopK":10,
"memory":true
} }

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +1,6 @@
// 本示例演示访问 DeepSeek 大模型 // 本示例演示访问 DeepSeek 大模型
package XDUMsgBot_cj package XDUMsgBot_cj
import std.collection.{ArrayList, reduce} // import std.collection.{ArrayList, reduce}
import std.io.StringReader import std.io.StringReader
import stdx.encoding.json.* import stdx.encoding.json.*
import stdx.net.http.* import stdx.net.http.*
@@ -27,7 +27,6 @@ class Embedding {
"dimensions": 1024 "dimensions": 1024
} }
''' '''
println(content)
let request = HttpRequestBuilder() let request = HttpRequestBuilder()
.url(url) .url(url)
.header('Authorization', 'Bearer ${key}') .header('Authorization', 'Bearer ${key}')
@@ -40,7 +39,6 @@ class Embedding {
func parse(text: String) { func parse(text: String) {
let json = JsonValue.fromStr(text).asObject() let json = JsonValue.fromStr(text).asObject()
println(json)
let data = json.getFields()['data'].asArray() let data = json.getFields()['data'].asArray()
let embedding = data[0].asObject().getFields()['embedding'].asArray() let embedding = data[0].asObject().getFields()['embedding'].asArray()
return embedding return embedding

View File

@@ -3,47 +3,12 @@ package XDUMsgBot_cj
import std.fs.* import std.fs.*
import std.io.* import std.io.*
import stdx.encoding.json.* import stdx.encoding.json.*
import stdx.net.http.*
// import stdx.net.tls.*
// class Config <: Serializable<Config> { func prepare(){
// var url:String = ""
// var key:String = ""
// var embeddingModel:String = ""
// var queryModel:String = ""
// public func serialize():DataModel{
// return DataModelStruct()
// .add(field<String>("url",url))
// .add(field<String>("key",key))
// .add(field<String>("embeddingModel",embeddingModel))
// .add(field<String>("queryModel",queryModel))
// }
// public static func deserialize(dm: DataModel): Config {
// var dms = match (dm) {
// case data: DataModelStruct => data
// case _ => throw Exception("this data is not DataModelStruct")
// }
// var result = Config()
// result.url = String.deserialize(dms.get("url"))
// result.key = String.deserialize(dms.get("key"))
// result.embeddingModel = String.deserialize(dms.get("embeddingModel"))
// result.queryModel = String.deserialize(dms.get("queryModel"))
// return result
// }
// }
let config = getConfig()
main() {
/* prepare and chunk data */
// open and read config file
let configPath:Path = Path("./config.json")
if(!exists(configPath)){
println("Error! config.json doesn't exist")
return
}
let configFile:File = File(configPath,Read)
let configBytes:Array<Byte> = readToEnd(configFile)
configFile.close()
let config = JsonValue.fromStr(String.fromUtf8(configBytes)).asObject()
// open and read data file // open and read data file
let dataPath:Path = Path("./data/data.txt") let dataPath:Path = Path("./data/data.txt")
@@ -59,25 +24,139 @@ main() {
let dataArray = dataString.split("\r\n") let dataArray = dataString.split("\r\n")
/* embedding */ /* embedding and store vector */
let embeddingModel = Embedding(url:config.getFields()['embeddingURL'].asString().getValue(), let embeddingModel = Embedding(url:config.getFields()['embeddingURL'].asString().getValue(),
key:config.getFields()['key'].asString().getValue(), key:config.getFields()['key'].asString().getValue(),
model:config.getFields()['embeddingModel'].asString().getValue()) model:config.getFields()['embeddingModel'].asString().getValue())
// open and write vectors
let dataEmbedPath:Path = Path("./data/data_embed.txt") // open and store vectors
if(!exists(dataEmbedPath)){ // let dataEmbedPath:Path = Path("./data/data_embed.txt")
println("Error! data/data_embed.txt doesn't exist") // if(!exists(dataEmbedPath)){
return // println("Error! data/data_embed.txt doesn't exist")
} // return
let dataEmbedFile:File = File(dataEmbedPath,Write) // }
dataEmbedFile.setLength(0) // let dataEmbedFile:File = File(dataEmbedPath,Append)
var i = 0 // dataEmbedFile.setLength(0)
for(data in dataArray){ for(data in dataArray){
let vector = embeddingModel.embed(data).toString() let vector = embeddingModel.embed(data).toString()
dataEmbedFile.write(vector.toArray())
i++ let client = ClientBuilder().build()
println(i) let content = '''
{ "embedding":${vector},
"document":"${data}"
}
'''
let request = HttpRequestBuilder()
.url(config.getFields()['vectorDBURL'].asString().getValue()+"/store")
.header('Content-Type', 'application/json')
.body(content)
.post()
.build()
let rsp = client.send(request)
// read response
let buf = Array<UInt8>(1024, repeat: 0)
let len = rsp.body.read(buf)
println(String.fromUtf8(buf.slice(0, len)))
client.close()
println("stored: ${data}")
}
}
func getKonwledge(input:String):String{
/* retrive */
// println("-------------------retrive:-----------------------")
let config = getConfig()
let embeddingModel = Embedding(url:config.getFields()['embeddingURL'].asString().getValue(),
key:config.getFields()['key'].asString().getValue(),
model:config.getFields()['embeddingModel'].asString().getValue())
let vector = embeddingModel.embed(input).toString()
let client = ClientBuilder().build()
let content = '''
{
"query_embedding":${vector},
"top_k":${config.getFields()['retriveTopK'].asInt().getValue()}
}
'''
let request = HttpRequestBuilder()
.url(config.getFields()['vectorDBURL'].asString().getValue()+"/query")
.header('Content-Type', 'application/json')
.body(content)
.post()
.build()
let rsp = client.send(request)
let output = StringReader(rsp.body).readToEnd()
let json = JsonValue.fromStr(output).asObject().getFields()['documents'].asArray()
client.close()
/* rerank */
// println("-------------------rerank:-----------------------")
let rerankModel = Rerank(url:config.getFields()['rerankURL'].asString().getValue(),
key:config.getFields()['key'].asString().getValue(),
model:config.getFields()['rerankModel'].asString().getValue())
let result = rerankModel.rerank(input,json,config.getFields()['rerankTopK'].asInt().getValue())
/* query*/
let baseKnowledge = StringBuilder()
for(item in result){
baseKnowledge.append("参考资料:")
baseKnowledge.append(item)
baseKnowledge.append("\r\n")
}
return baseKnowledge.toString()
}
main() {
// just need to run once
// prepare()
let config = getConfig()
let robot = Query(url: config.getFields()['queryURL'].asString().getValue(),
key: config.getFields()['key'].asString().getValue(),
model: config.getFields()['queryModel'].asString().getValue(),
memory: config.getFields()['memory'].asBool().getValue())
let input = readln()
let baseKnowledge = getKonwledge(input)
robot.preset(input,baseKnowledge)
let reply = robot.chats(input)
println(reply)
println("\n\n------------------回答结束,如果想聊聊新话题,可以输入“新对话#<新话题>”开始新的对话------------------\n\n")
while (true) {
let input = readln()
if (input.startsWith('新对话#')) {
let prompt = input.trimStart('新对话#')
let baseKnowledge = getKonwledge(prompt)
robot.preset(prompt,baseKnowledge)
println("--------新对话: 关于问题:${prompt}-------------")
let reply = robot.chats(input)
println(reply)
println("\n\n------------------回答结束,如果想聊聊新话题,可以输入“新对话#<新话题>”开始新的对话------------------\n\n")
} else {
let reply = robot.chats(input)
println(reply)
println("\n\n------------------回答结束,如果想聊聊新话题,可以输入“新对话#<新话题>”开始新的对话------------------\n\n")
}
} }
@@ -90,32 +169,11 @@ main() {
// // 使用 SiliconFlow 提供的服务接口
// let robot = LLM(url: 'https://api.siliconflow.cn/v1/chat/completions',
// // 如果示例自带的密钥失效请自行注册https://cloud.siliconflow.cn/account/ak
// key: 'sk-xsoegkpdvqlbsoodrnaygqycdvhplkyowivkzlszqfytpvti',
// model: 'Qwen/Qwen3-8B',
// memory: true)
// // robot.preset('我会用林黛玉的风格回复哥哥的所有问题')
// // robot.chats('介绍李白')
// // println('\n----------\n')
// // robot.chats('他和安徽的不解情缘')
// // println('\n----------\n')
// // robot.reset()
// // robot.chat('你好') |> println
// // robot.chat('却是荷池跳雨,散了真珠还聚') |> println
// while (true) {
// let input = readln()
// if (input.startsWith('风格#')) {
// let style = input.trimStart('风格#')
// robot.switchStyle(style)
// } else {
// let reply = robot.chats(input)
// println(reply)
// }
// }
} }

View File

@@ -1,4 +1,3 @@
// 本示例演示访问 DeepSeek 大模型
package XDUMsgBot_cj package XDUMsgBot_cj
import std.collection.{ArrayList, reduce} import std.collection.{ArrayList, reduce}
import std.io.StringReader import std.io.StringReader
@@ -31,10 +30,10 @@ extend ArrayList<String> {
} }
} }
class LLM { class Query {
let client: Client let client: Client
let history = ArrayList<String>() let history = ArrayList<String>()
public LLM(let url!: String, let key!: String, let model!: String, public Query(let url!: String, let key!: String, let model!: String,
var memory!: Bool = false) { var memory!: Bool = false) {
var config = TlsClientConfig() var config = TlsClientConfig()
config.verifyMode = TrustAll config.verifyMode = TrustAll
@@ -112,17 +111,22 @@ class LLM {
} }
// 角色预设或加载历史对话 // 角色预设或加载历史对话
public func preset(content: String, role!: Role = System) { public func preset(query: String,baseKnowledge:String, role!: Role = System) {
history.add(role, content) history.clear()
history.add(role, """
你是一位专业的知识助手名字叫XDUMsgBot你可以根据相关片段中的信息回答用户关于西安电子科技大学的问题。
请根据用户的问题和下列片段生成准确的回应。
用户问题:${query}
相关片段:
${baseKnowledge}
请基于上述内容作答,如果没有明确的信息可供参考,请回答不知道,不要编造信息。""")
memory = true memory = true
} }
public func reset() { public func reset() {
history.clear() history.clear()
} }
public func switchStyle(styleName:String){
reset()
preset("用${styleName}的风格回复问题")
}
} }

56
src/rerank.cj Normal file
View File

@@ -0,0 +1,56 @@
package XDUMsgBot_cj
import std.collection.ArrayList
import std.io.StringReader
import stdx.encoding.json.*
import stdx.net.http.*
import stdx.net.tls.*
class Rerank {
let client: Client
public Rerank(let url!: String, let key!: String, let model!: String) {
var config = TlsClientConfig()
config.verifyMode = TrustAll
client = ClientBuilder()
.tlsConfig(config)
// AI 服务响应有时候比较慢,这里设置为无限等待
.readTimeout(Duration.Max)
.build()
}
func send(input: String,documents:JsonArray,topk:Int) {
let content = '''
{ "model":"${model}",
"query":"${input}",
"documents": ${documents},
"instruction": "Please rerank the documents based on the query.",
"top_n": ${topk},
"return_documents": true
}
'''
let request = HttpRequestBuilder()
.url(url)
.header('Authorization', 'Bearer ${key}')
.header('Content-Type', 'application/json')
.body(content)
.post()
.build()
client.send(request)
}
public func rerank(input: String,documents:JsonArray,topk:Int) {
let response = send(input,documents,topk)
let output = StringReader(response.body).readToEnd()
let resultArray = JsonValue.fromStr(output).asObject().getFields()["results"].asArray().getItems()
let list = ArrayList<String>()
for(result in resultArray){
let jsonObject = result.asObject()
let text = jsonObject.getFields()["document"].asObject().getFields()["text"].asString()
list.add(text.toString())
}
return list
}
}

18
src/utils.cj Normal file
View File

@@ -0,0 +1,18 @@
package XDUMsgBot_cj
import std.fs.*
import std.io.*
import stdx.encoding.json.*
func getConfig():JsonObject {
// open and read config file
let configPath:Path = Path("./config.json")
if(!exists(configPath)){
println("Error! config.json doesn't exist")
}
let configFile:File = File(configPath,Read)
let configBytes:Array<Byte> = readToEnd(configFile)
configFile.close()
let config = JsonValue.fromStr(String.fromUtf8(configBytes)).asObject()
return config
}