doorcore 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- doorcore-0.1.0/PKG-INFO +66 -0
- doorcore-0.1.0/README.md +46 -0
- doorcore-0.1.0/pyproject.toml +42 -0
- doorcore-0.1.0/src/doorcore/__init__.py +9 -0
- doorcore-0.1.0/src/doorcore/api.py +127 -0
- doorcore-0.1.0/src/doorcore/cli.py +57 -0
- doorcore-0.1.0/src/doorcore/core.py +454 -0
- doorcore-0.1.0/src/doorcore/data/config.json +3 -0
- doorcore-0.1.0/src/doorcore/data/corpus//347/237/245/350/257/206/345/272/223.txt +5045 -0
- doorcore-0.1.0/src/doorcore/data/corpus//347/254/221/350/257/235.txt +569 -0
- doorcore-0.1.0/src/doorcore/data/model.pkl +0 -0
doorcore-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: doorcore
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: door-Core v2: a lightweight offline Chinese AI engine with Markov generation and cosine retrieval
|
|
5
|
+
Project-URL: Homepage, https://github.com/door-studio/doorcore
|
|
6
|
+
Author-email: door 工作室 <doornb666@outlook.com>
|
|
7
|
+
License: MIT
|
|
8
|
+
Keywords: ai,chatbot,chinese,markov,offline
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Requires-Python: >=3.9
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# doorcore
|
|
22
|
+
|
|
23
|
+
door-Core v2 本地 AI 引擎,纯 Python 标准库、零第三方依赖。
|
|
24
|
+
|
|
25
|
+
特性:
|
|
26
|
+
- 中文关键词知识库检索(余弦相似)
|
|
27
|
+
- 字符级马尔可夫生成
|
|
28
|
+
- 中文/口语算式安全计算:支持「二乘四」「2的10次方」「根号2」「5的平方」等
|
|
29
|
+
- 报时、讲笑话、简单角色扮演
|
|
30
|
+
- OpenAI 兼容 API(`/v1/chat/completions`)
|
|
31
|
+
|
|
32
|
+
## 安装
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pip install doorcore
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## 编程使用
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
from doorcore import DoorCore
|
|
42
|
+
|
|
43
|
+
ai = DoorCore()
|
|
44
|
+
ai.load_or_train()
|
|
45
|
+
|
|
46
|
+
print(ai.ask("1+1等于几")) # 计算
|
|
47
|
+
print(ai.ask("雷军是谁")) # 知识库
|
|
48
|
+
print(ai.ask("讲个笑话")) # 笑话
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## 命令行
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
doorcore # 启动交互式聊天
|
|
55
|
+
doorcore-api # 启动 OpenAI 兼容 API 服务
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
API 默认监听 `http://localhost:8765/v1`,模型名 `door-core`。
|
|
59
|
+
可在任何支持自定义 OpenAI 兼容模型的客户端里填入:
|
|
60
|
+
- Base URL: `http://localhost:8765/v1`
|
|
61
|
+
- Model: `door-core`
|
|
62
|
+
- API Key: 任意填(不校验)
|
|
63
|
+
|
|
64
|
+
## 许可证
|
|
65
|
+
|
|
66
|
+
MIT
|
doorcore-0.1.0/README.md
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# doorcore
|
|
2
|
+
|
|
3
|
+
door-Core v2 本地 AI 引擎,纯 Python 标准库、零第三方依赖。
|
|
4
|
+
|
|
5
|
+
特性:
|
|
6
|
+
- 中文关键词知识库检索(余弦相似)
|
|
7
|
+
- 字符级马尔可夫生成
|
|
8
|
+
- 中文/口语算式安全计算:支持「二乘四」「2的10次方」「根号2」「5的平方」等
|
|
9
|
+
- 报时、讲笑话、简单角色扮演
|
|
10
|
+
- OpenAI 兼容 API(`/v1/chat/completions`)
|
|
11
|
+
|
|
12
|
+
## 安装
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pip install doorcore
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## 编程使用
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
from doorcore import DoorCore
|
|
22
|
+
|
|
23
|
+
ai = DoorCore()
|
|
24
|
+
ai.load_or_train()
|
|
25
|
+
|
|
26
|
+
print(ai.ask("1+1等于几")) # 计算
|
|
27
|
+
print(ai.ask("雷军是谁")) # 知识库
|
|
28
|
+
print(ai.ask("讲个笑话")) # 笑话
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## 命令行
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
doorcore # 启动交互式聊天
|
|
35
|
+
doorcore-api # 启动 OpenAI 兼容 API 服务
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
API 默认监听 `http://localhost:8765/v1`,模型名 `door-core`。
|
|
39
|
+
可在任何支持自定义 OpenAI 兼容模型的客户端里填入:
|
|
40
|
+
- Base URL: `http://localhost:8765/v1`
|
|
41
|
+
- Model: `door-core`
|
|
42
|
+
- API Key: 任意填(不校验)
|
|
43
|
+
|
|
44
|
+
## 许可证
|
|
45
|
+
|
|
46
|
+
MIT
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "doorcore"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "door-Core v2: a lightweight offline Chinese AI engine with Markov generation and cosine retrieval"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = {text = "MIT"}
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "door 工作室", email = "doornb666@outlook.com"},
|
|
14
|
+
]
|
|
15
|
+
keywords = ["ai", "chatbot", "offline", "chinese", "markov"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 4 - Beta",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3.9",
|
|
20
|
+
"Programming Language :: Python :: 3.10",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Programming Language :: Python :: 3.13",
|
|
24
|
+
"License :: OSI Approved :: MIT License",
|
|
25
|
+
"Operating System :: OS Independent",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[project.urls]
|
|
29
|
+
Homepage = "https://github.com/door-studio/doorcore"
|
|
30
|
+
|
|
31
|
+
[project.scripts]
|
|
32
|
+
doorcore = "doorcore.cli:main"
|
|
33
|
+
doorcore-api = "doorcore.api:main"
|
|
34
|
+
|
|
35
|
+
[tool.hatch.build]
|
|
36
|
+
include = [
|
|
37
|
+
"src/doorcore/**/*.py",
|
|
38
|
+
"src/doorcore/data/**/*",
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
[tool.hatch.build.targets.wheel]
|
|
42
|
+
packages = ["src/doorcore"]
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# door-core v2 —— 零依赖 OpenAI 兼容 API + 可部署 WSGI application
|
|
2
|
+
# 本地运行: python api.py -> http://localhost:8765/v1
|
|
3
|
+
# 公网部署: 平台 import 本文件的 application(WSGI)即可,模型名 door-core
|
|
4
|
+
import os
|
|
5
|
+
import json
|
|
6
|
+
import importlib.resources as _resources
|
|
7
|
+
|
|
8
|
+
from http.server import ThreadingHTTPServer, BaseHTTPRequestHandler
|
|
9
|
+
from doorcore.core import DoorCore
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _pkg_path(*parts):
|
|
13
|
+
try:
|
|
14
|
+
return str(_resources.files("doorcore").joinpath(*parts))
|
|
15
|
+
except Exception:
|
|
16
|
+
return os.path.join(os.path.dirname(os.path.abspath(__file__)), *parts)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def load_config():
|
|
20
|
+
p = _pkg_path("data", "config.json")
|
|
21
|
+
if os.path.exists(p):
|
|
22
|
+
try:
|
|
23
|
+
return json.load(open(p, encoding="utf-8"))
|
|
24
|
+
except Exception:
|
|
25
|
+
pass
|
|
26
|
+
return {"n": 3}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
cfg = load_config()
|
|
30
|
+
_AI = None
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def get_ai():
|
|
34
|
+
global _AI
|
|
35
|
+
if _AI is None:
|
|
36
|
+
n = int(cfg.get("n", 3))
|
|
37
|
+
_AI = DoorCore(n=n)
|
|
38
|
+
_AI.load_or_train()
|
|
39
|
+
return _AI
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _chat_reply(messages):
|
|
43
|
+
text = ""
|
|
44
|
+
for m in reversed(messages or []):
|
|
45
|
+
if m.get("role") == "user":
|
|
46
|
+
text = m.get("content", "")
|
|
47
|
+
break
|
|
48
|
+
return get_ai().ask(text)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _completion_obj(reply):
|
|
52
|
+
return {
|
|
53
|
+
"object": "chat.completion",
|
|
54
|
+
"model": "door-core",
|
|
55
|
+
"choices": [{
|
|
56
|
+
"index": 0,
|
|
57
|
+
"message": {"role": "assistant", "content": reply},
|
|
58
|
+
"finish_reason": "stop",
|
|
59
|
+
}],
|
|
60
|
+
"usage": {"prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0},
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _body_str(obj):
|
|
65
|
+
return json.dumps(obj, ensure_ascii=False).encode("utf-8")
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class Handler(BaseHTTPRequestHandler):
|
|
69
|
+
def _send(self, code, obj):
|
|
70
|
+
body = _body_str(obj)
|
|
71
|
+
self.send_response(code)
|
|
72
|
+
self.send_header("Content-Type", "application/json; charset=utf-8")
|
|
73
|
+
self.send_header("Content-Length", str(len(body)))
|
|
74
|
+
self.end_headers()
|
|
75
|
+
self.wfile.write(body)
|
|
76
|
+
|
|
77
|
+
def do_POST(self):
|
|
78
|
+
if self.path.rstrip("/") not in ("/v1/chat/completions", "/chat/completions"):
|
|
79
|
+
self._send(404, {"error": "not found"})
|
|
80
|
+
return
|
|
81
|
+
try:
|
|
82
|
+
ln = int(self.headers.get("Content-Length", 0) or 0)
|
|
83
|
+
raw = self.rfile.read(ln) if ln else b""
|
|
84
|
+
data = json.loads(raw.decode("utf-8"))
|
|
85
|
+
except Exception as e:
|
|
86
|
+
self._send(400, {"error": str(e)})
|
|
87
|
+
return
|
|
88
|
+
self._send(200, _completion_obj(_chat_reply(data.get("messages", []))))
|
|
89
|
+
|
|
90
|
+
def log_message(self, *a):
|
|
91
|
+
pass
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def application(environ, start_response):
|
|
95
|
+
path = (environ.get("PATH_INFO", "") or "").rstrip("/")
|
|
96
|
+
if environ.get("REQUEST_METHOD") == "POST" and path in (
|
|
97
|
+
"/v1/chat/completions", "/chat/completions"):
|
|
98
|
+
try:
|
|
99
|
+
ln = int(environ.get("CONTENT_LENGTH", 0) or 0)
|
|
100
|
+
raw = environ["wsgi.input"].read(ln) if ln else b""
|
|
101
|
+
data = json.loads(raw.decode("utf-8"))
|
|
102
|
+
reply = _chat_reply(data.get("messages", []))
|
|
103
|
+
except Exception as e:
|
|
104
|
+
reply = "出错了:" + str(e)
|
|
105
|
+
body = _body_str(_completion_obj(reply))
|
|
106
|
+
start_response("200 OK", [
|
|
107
|
+
("Content-Type", "application/json; charset=utf-8"),
|
|
108
|
+
("Content-Length", str(len(body))),
|
|
109
|
+
])
|
|
110
|
+
return [body]
|
|
111
|
+
body = b'{"error":"not found"}'
|
|
112
|
+
start_response("404 Not Found", [
|
|
113
|
+
("Content-Type", "application/json"),
|
|
114
|
+
("Content-Length", str(len(body))),
|
|
115
|
+
])
|
|
116
|
+
return [body]
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def main():
|
|
120
|
+
port = int(os.environ.get("PORT", "8765"))
|
|
121
|
+
print(f"[door-core v2 API] http://localhost:{port}/v1 (模型名 door-core)")
|
|
122
|
+
httpd = ThreadingHTTPServer(("0.0.0.0", port), Handler)
|
|
123
|
+
httpd.serve_forever()
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
if __name__ == "__main__":
|
|
127
|
+
main()
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# door-core v2 —— 命令行聊天(训练并从 .txt 语料加载,结果存 model.pkl)
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
import json
|
|
5
|
+
import importlib.resources as _resources
|
|
6
|
+
|
|
7
|
+
from doorcore.core import DoorCore
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _pkg_path(*parts):
|
|
11
|
+
try:
|
|
12
|
+
return str(_resources.files("doorcore").joinpath(*parts))
|
|
13
|
+
except Exception:
|
|
14
|
+
return os.path.join(os.path.dirname(os.path.abspath(__file__)), *parts)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def load_config():
|
|
18
|
+
p = _pkg_path("data", "config.json")
|
|
19
|
+
if os.path.exists(p):
|
|
20
|
+
try:
|
|
21
|
+
return json.load(open(p, encoding="utf-8"))
|
|
22
|
+
except Exception:
|
|
23
|
+
pass
|
|
24
|
+
return {"n": 3}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def main():
|
|
28
|
+
cfg = load_config()
|
|
29
|
+
n = int(cfg.get("n", 3))
|
|
30
|
+
ai = DoorCore(n=n)
|
|
31
|
+
|
|
32
|
+
force = "--retrain" in sys.argv
|
|
33
|
+
status = ai.load_or_train(force=force)
|
|
34
|
+
print(f"[door-core v2] 模型: {status} | 知识条目: {len(ai.kb)} | "
|
|
35
|
+
f"马尔可夫节点: {len(ai.model)}")
|
|
36
|
+
print("聊天开始(/retrain 重新训练, /quit 退出)")
|
|
37
|
+
|
|
38
|
+
while True:
|
|
39
|
+
try:
|
|
40
|
+
u = input("你> ").strip()
|
|
41
|
+
except (EOFError, KeyboardInterrupt):
|
|
42
|
+
print()
|
|
43
|
+
break
|
|
44
|
+
if not u:
|
|
45
|
+
continue
|
|
46
|
+
if u in ("/quit", "/exit"):
|
|
47
|
+
break
|
|
48
|
+
if u == "/retrain":
|
|
49
|
+
ai.load_corpus()
|
|
50
|
+
ai.save_model()
|
|
51
|
+
print(f"已重新训练:知识条目 {len(ai.kb)},马尔可夫节点 {len(ai.model)}")
|
|
52
|
+
continue
|
|
53
|
+
print("AI> " + ai.ask(u))
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
if __name__ == "__main__":
|
|
57
|
+
main()
|