termux-aichain 1.0.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.
- termux_aichain-1.0.0/LICENSE +17 -0
- termux_aichain-1.0.0/PKG-INFO +31 -0
- termux_aichain-1.0.0/README.md +0 -0
- termux_aichain-1.0.0/pyproject.toml +43 -0
- termux_aichain-1.0.0/setup.cfg +4 -0
- termux_aichain-1.0.0/setup.py +24 -0
- termux_aichain-1.0.0/termux_aichain/__init__.py +149 -0
- termux_aichain-1.0.0/termux_aichain/cli.py +128 -0
- termux_aichain-1.0.0/termux_aichain/core/__init__.py +79 -0
- termux_aichain-1.0.0/termux_aichain/core/base.py +144 -0
- termux_aichain-1.0.0/termux_aichain/core/parsers.py +126 -0
- termux_aichain-1.0.0/termux_aichain/core/prompt.py +126 -0
- termux_aichain-1.0.0/termux_aichain/core/providers/__init__.py +23 -0
- termux_aichain-1.0.0/termux_aichain/core/providers/bitnet.py +31 -0
- termux_aichain-1.0.0/termux_aichain/core/providers/local_server.py +148 -0
- termux_aichain-1.0.0/termux_aichain/core/providers/openai_compatible.py +199 -0
- termux_aichain-1.0.0/termux_aichain/core/schema.py +103 -0
- termux_aichain-1.0.0/termux_aichain/core/splitters.py +236 -0
- termux_aichain-1.0.0/termux_aichain/device/__init__.py +29 -0
- termux_aichain-1.0.0/termux_aichain/device/ecosystem.py +97 -0
- termux_aichain-1.0.0/termux_aichain/device/tools.py +234 -0
- termux_aichain-1.0.0/termux_aichain/graph/__init__.py +27 -0
- termux_aichain-1.0.0/termux_aichain/graph/agent.py +118 -0
- termux_aichain-1.0.0/termux_aichain/graph/state.py +189 -0
- termux_aichain-1.0.0/termux_aichain/memory/__init__.py +16 -0
- termux_aichain-1.0.0/termux_aichain/memory/buffer.py +44 -0
- termux_aichain-1.0.0/termux_aichain/memory/extractor.py +39 -0
- termux_aichain-1.0.0/termux_aichain/memory/sqlite.py +159 -0
- termux_aichain-1.0.0/termux_aichain/serve/__init__.py +14 -0
- termux_aichain-1.0.0/termux_aichain/serve/dashboard.py +250 -0
- termux_aichain-1.0.0/termux_aichain/serve/server.py +223 -0
- termux_aichain-1.0.0/termux_aichain/trace/__init__.py +13 -0
- termux_aichain-1.0.0/termux_aichain/trace/tracer.py +142 -0
- termux_aichain-1.0.0/termux_aichain.egg-info/PKG-INFO +31 -0
- termux_aichain-1.0.0/termux_aichain.egg-info/SOURCES.txt +52 -0
- termux_aichain-1.0.0/termux_aichain.egg-info/dependency_links.txt +1 -0
- termux_aichain-1.0.0/termux_aichain.egg-info/entry_points.txt +2 -0
- termux_aichain-1.0.0/termux_aichain.egg-info/top_level.txt +1 -0
- termux_aichain-1.0.0/tests/test_core_bitnet.py +6 -0
- termux_aichain-1.0.0/tests/test_core_chain.py +42 -0
- termux_aichain-1.0.0/tests/test_core_parser.py +44 -0
- termux_aichain-1.0.0/tests/test_core_prompt.py +47 -0
- termux_aichain-1.0.0/tests/test_core_provider.py +103 -0
- termux_aichain-1.0.0/tests/test_core_splitter.py +41 -0
- termux_aichain-1.0.0/tests/test_dashboard.py +42 -0
- termux_aichain-1.0.0/tests/test_device.py +61 -0
- termux_aichain-1.0.0/tests/test_ecosystem.py +32 -0
- termux_aichain-1.0.0/tests/test_graph_agent.py +64 -0
- termux_aichain-1.0.0/tests/test_graph_state.py +109 -0
- termux_aichain-1.0.0/tests/test_local_server.py +76 -0
- termux_aichain-1.0.0/tests/test_memory.py +95 -0
- termux_aichain-1.0.0/tests/test_microscopic_edge_cases.py +145 -0
- termux_aichain-1.0.0/tests/test_serve.py +66 -0
- termux_aichain-1.0.0/tests/test_trace.py +58 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
Copyright 2026 UnoKim & AMEVA Open-Source Foundation
|
|
6
|
+
|
|
7
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
|
+
you may not use this file except in compliance with the License.
|
|
9
|
+
You may obtain a copy of the License at
|
|
10
|
+
|
|
11
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
|
|
13
|
+
Unless required by applicable law or agreed to in writing, software
|
|
14
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
+
See the License for the specific language governing permissions and
|
|
17
|
+
limitations under the License.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: termux-aichain
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Sovereign Zero-Dependency AI Chaining and Multimodal Autonomous Agent Framework for Android Edge and Termux
|
|
5
|
+
Home-page: https://github.com/uno-km/termux-aichain
|
|
6
|
+
Author: UnoKim
|
|
7
|
+
Author-email: UnoKim <uno-km@users.noreply.github.com>
|
|
8
|
+
License: Apache-2.0
|
|
9
|
+
Project-URL: Homepage, https://github.com/uno-km/termux-aichain
|
|
10
|
+
Project-URL: Repository, https://github.com/uno-km/termux-aichain.git
|
|
11
|
+
Project-URL: Issues, https://github.com/uno-km/termux-aichain/issues
|
|
12
|
+
Keywords: termux,android,edge-ai,langchain,llama-cpp,bitnet,agent,zero-dependency
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
21
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
22
|
+
Classifier: Operating System :: Android
|
|
23
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
24
|
+
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
25
|
+
Requires-Python: >=3.10
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
License-File: LICENSE
|
|
28
|
+
Dynamic: author
|
|
29
|
+
Dynamic: home-page
|
|
30
|
+
Dynamic: license-file
|
|
31
|
+
Dynamic: requires-python
|
|
File without changes
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "termux-aichain"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "Sovereign Zero-Dependency AI Chaining and Multimodal Autonomous Agent Framework for Android Edge and Termux"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
authors = [{ name = "UnoKim", email = "uno-km@users.noreply.github.com" }]
|
|
11
|
+
license = { text = "Apache-2.0" }
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Development Status :: 4 - Beta",
|
|
14
|
+
"Intended Audience :: Developers",
|
|
15
|
+
"License :: OSI Approved :: Apache Software License",
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"Programming Language :: Python :: 3.10",
|
|
18
|
+
"Programming Language :: Python :: 3.11",
|
|
19
|
+
"Programming Language :: Python :: 3.12",
|
|
20
|
+
"Programming Language :: Python :: 3.14",
|
|
21
|
+
"Operating System :: POSIX :: Linux",
|
|
22
|
+
"Operating System :: Android",
|
|
23
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
24
|
+
"Topic :: Software Development :: Libraries :: Application Frameworks",
|
|
25
|
+
]
|
|
26
|
+
keywords = ["termux", "android", "edge-ai", "langchain", "llama-cpp", "bitnet", "agent", "zero-dependency"]
|
|
27
|
+
requires-python = ">=3.10"
|
|
28
|
+
dependencies = []
|
|
29
|
+
|
|
30
|
+
[project.urls]
|
|
31
|
+
Homepage = "https://github.com/uno-km/termux-aichain"
|
|
32
|
+
Repository = "https://github.com/uno-km/termux-aichain.git"
|
|
33
|
+
Issues = "https://github.com/uno-km/termux-aichain/issues"
|
|
34
|
+
|
|
35
|
+
[tool.setuptools.packages.find]
|
|
36
|
+
where = ["."]
|
|
37
|
+
include = ["termux_aichain*"]
|
|
38
|
+
|
|
39
|
+
[tool.pytest.ini_options]
|
|
40
|
+
pythonpath = ["."]
|
|
41
|
+
testpaths = ["tests"]
|
|
42
|
+
[project.scripts]
|
|
43
|
+
termux-aichain = "termux_aichain.cli:main"
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
|
|
3
|
+
setup(
|
|
4
|
+
name="termux-aichain",
|
|
5
|
+
version="0.1.0",
|
|
6
|
+
description="Ultra-lightweight Zero-Dependency AI chaining & agent framework for Termux, Android and Edge computing.",
|
|
7
|
+
long_description=open("README.md", encoding="utf-8").read() if open("README.md", encoding="utf-8") else "",
|
|
8
|
+
long_description_content_type="text/markdown",
|
|
9
|
+
author="UnoKim",
|
|
10
|
+
author_email="uno-km@users.noreply.github.com",
|
|
11
|
+
url="https://github.com/uno-km/termux-aichain",
|
|
12
|
+
packages=find_packages(),
|
|
13
|
+
python_requires=">=3.10",
|
|
14
|
+
install_requires=[],
|
|
15
|
+
classifiers=[
|
|
16
|
+
"License :: OSI Approved :: Apache Software License",
|
|
17
|
+
"Operating System :: Android",
|
|
18
|
+
"Operating System :: POSIX :: Linux",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3.10",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
],
|
|
24
|
+
)
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"""
|
|
2
|
+
==============================================================================
|
|
3
|
+
termux-aichain: Sovereign Zero-Dependency Edge AI Framework for Termux & Android
|
|
4
|
+
==============================================================================
|
|
5
|
+
Copyright (c) 2026 AMEVA Open-Source Foundation & UnoKim.
|
|
6
|
+
Licensed under the Apache License, Version 2.0.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
__version__ = "1.0.0"
|
|
10
|
+
__author__ = "UnoKim <uno-km@users.noreply.github.com>"
|
|
11
|
+
|
|
12
|
+
from termux_aichain.core.schema import (
|
|
13
|
+
Message,
|
|
14
|
+
HumanMessage,
|
|
15
|
+
AIMessage,
|
|
16
|
+
SystemMessage,
|
|
17
|
+
ToolMessage,
|
|
18
|
+
UsageInfo,
|
|
19
|
+
GenerationResult,
|
|
20
|
+
StreamChunk,
|
|
21
|
+
)
|
|
22
|
+
from termux_aichain.core.prompt import (
|
|
23
|
+
PromptTemplate,
|
|
24
|
+
ChatPromptTemplate,
|
|
25
|
+
)
|
|
26
|
+
from termux_aichain.core.base import (
|
|
27
|
+
Runnable,
|
|
28
|
+
RunnableLambda,
|
|
29
|
+
RunnableSequence,
|
|
30
|
+
BaseChatModel,
|
|
31
|
+
)
|
|
32
|
+
from termux_aichain.core.providers.openai_compatible import OpenAICompatibleChat
|
|
33
|
+
from termux_aichain.core.providers.bitnet import BitNetChat
|
|
34
|
+
from termux_aichain.core.providers.local_server import (
|
|
35
|
+
LocalServerConfig,
|
|
36
|
+
LocalServerManager,
|
|
37
|
+
LlamaCppServer,
|
|
38
|
+
BitNetServer,
|
|
39
|
+
)
|
|
40
|
+
from termux_aichain.core.parsers import (
|
|
41
|
+
StringOutputParser,
|
|
42
|
+
JsonOutputParser,
|
|
43
|
+
RegexOutputParser,
|
|
44
|
+
)
|
|
45
|
+
from termux_aichain.core.splitters import (
|
|
46
|
+
Document,
|
|
47
|
+
CharacterTextSplitter,
|
|
48
|
+
RecursiveCharacterTextSplitter,
|
|
49
|
+
TextLoader,
|
|
50
|
+
MarkdownLoader,
|
|
51
|
+
JSONLoader,
|
|
52
|
+
)
|
|
53
|
+
from termux_aichain.graph.state import (
|
|
54
|
+
StateGraph,
|
|
55
|
+
CompiledGraph,
|
|
56
|
+
START,
|
|
57
|
+
END,
|
|
58
|
+
)
|
|
59
|
+
from termux_aichain.graph.agent import (
|
|
60
|
+
Tool,
|
|
61
|
+
tool,
|
|
62
|
+
create_react_agent,
|
|
63
|
+
)
|
|
64
|
+
from termux_aichain.memory.buffer import ConversationBufferMemory
|
|
65
|
+
from termux_aichain.memory.sqlite import SQLiteEntityMemory, SQLiteVectorStore
|
|
66
|
+
from termux_aichain.memory.extractor import FactExtractor
|
|
67
|
+
from termux_aichain.serve.server import AgentServer, serve
|
|
68
|
+
from termux_aichain.serve.dashboard import DASHBOARD_HTML
|
|
69
|
+
from termux_aichain.trace.tracer import TraceSpan, Tracer, traceable
|
|
70
|
+
from termux_aichain.device.tools import (
|
|
71
|
+
get_battery_status,
|
|
72
|
+
get_sensor_data,
|
|
73
|
+
get_device_location,
|
|
74
|
+
record_speech_to_text,
|
|
75
|
+
vibrate_device,
|
|
76
|
+
send_notification,
|
|
77
|
+
speak_tts,
|
|
78
|
+
execute_shell,
|
|
79
|
+
get_default_device_tools,
|
|
80
|
+
)
|
|
81
|
+
from termux_aichain.device.ecosystem import (
|
|
82
|
+
transcribe_speech,
|
|
83
|
+
generate_diffusion_image,
|
|
84
|
+
browse_web_headless,
|
|
85
|
+
get_ecosystem_tools,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
__all__ = [
|
|
89
|
+
"__version__",
|
|
90
|
+
"Message",
|
|
91
|
+
"HumanMessage",
|
|
92
|
+
"AIMessage",
|
|
93
|
+
"SystemMessage",
|
|
94
|
+
"ToolMessage",
|
|
95
|
+
"UsageInfo",
|
|
96
|
+
"GenerationResult",
|
|
97
|
+
"StreamChunk",
|
|
98
|
+
"PromptTemplate",
|
|
99
|
+
"ChatPromptTemplate",
|
|
100
|
+
"Runnable",
|
|
101
|
+
"RunnableLambda",
|
|
102
|
+
"RunnableSequence",
|
|
103
|
+
"BaseChatModel",
|
|
104
|
+
"OpenAICompatibleChat",
|
|
105
|
+
"BitNetChat",
|
|
106
|
+
"LocalServerConfig",
|
|
107
|
+
"LocalServerManager",
|
|
108
|
+
"LlamaCppServer",
|
|
109
|
+
"BitNetServer",
|
|
110
|
+
"StringOutputParser",
|
|
111
|
+
"JsonOutputParser",
|
|
112
|
+
"RegexOutputParser",
|
|
113
|
+
"Document",
|
|
114
|
+
"CharacterTextSplitter",
|
|
115
|
+
"RecursiveCharacterTextSplitter",
|
|
116
|
+
"TextLoader",
|
|
117
|
+
"MarkdownLoader",
|
|
118
|
+
"JSONLoader",
|
|
119
|
+
"StateGraph",
|
|
120
|
+
"CompiledGraph",
|
|
121
|
+
"START",
|
|
122
|
+
"END",
|
|
123
|
+
"Tool",
|
|
124
|
+
"tool",
|
|
125
|
+
"create_react_agent",
|
|
126
|
+
"ConversationBufferMemory",
|
|
127
|
+
"SQLiteEntityMemory",
|
|
128
|
+
"SQLiteVectorStore",
|
|
129
|
+
"FactExtractor",
|
|
130
|
+
"AgentServer",
|
|
131
|
+
"serve",
|
|
132
|
+
"DASHBOARD_HTML",
|
|
133
|
+
"TraceSpan",
|
|
134
|
+
"Tracer",
|
|
135
|
+
"traceable",
|
|
136
|
+
"get_battery_status",
|
|
137
|
+
"get_sensor_data",
|
|
138
|
+
"get_device_location",
|
|
139
|
+
"record_speech_to_text",
|
|
140
|
+
"vibrate_device",
|
|
141
|
+
"send_notification",
|
|
142
|
+
"speak_tts",
|
|
143
|
+
"execute_shell",
|
|
144
|
+
"get_default_device_tools",
|
|
145
|
+
"transcribe_speech",
|
|
146
|
+
"generate_diffusion_image",
|
|
147
|
+
"browse_web_headless",
|
|
148
|
+
"get_ecosystem_tools",
|
|
149
|
+
]
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""
|
|
2
|
+
==============================================================================
|
|
3
|
+
termux-aichain Unified Command Line Interface & One-Touch Provisioner
|
|
4
|
+
==============================================================================
|
|
5
|
+
Provides sovereign zero-state setup, environment diagnostics, model pull,
|
|
6
|
+
and 1-line serving CLI for Android Termux.
|
|
7
|
+
Zero external heavy dependencies - Pure Python 3.10+ standard library.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
import os
|
|
12
|
+
import sys
|
|
13
|
+
import shutil
|
|
14
|
+
import argparse
|
|
15
|
+
import subprocess
|
|
16
|
+
import urllib.request
|
|
17
|
+
from termux_aichain import __version__, serve, PromptTemplate, LocalServerConfig, LlamaCppServer
|
|
18
|
+
|
|
19
|
+
MODELS_REGISTRY = {
|
|
20
|
+
"llama-3.2-3b": {
|
|
21
|
+
"url": "https://huggingface.co/bartowski/Llama-3.2-3B-Instruct-GGUF/resolve/main/Llama-3.2-3B-Instruct-Q4_K_M.gguf",
|
|
22
|
+
"filename": "Llama-3.2-3B-Instruct-Q4_K_M.gguf",
|
|
23
|
+
"size_desc": "~1.9 GB",
|
|
24
|
+
},
|
|
25
|
+
"qwen-2.5-1.5b": {
|
|
26
|
+
"url": "https://huggingface.co/Qwen/Qwen2.5-1.5B-Instruct-GGUF/resolve/main/qwen2.5-1.5b-instruct-q4_k_m.gguf",
|
|
27
|
+
"filename": "qwen2.5-1.5b-instruct-q4_k_m.gguf",
|
|
28
|
+
"size_desc": "~0.98 GB",
|
|
29
|
+
},
|
|
30
|
+
"bitnet-3b": {
|
|
31
|
+
"url": "https://huggingface.co/1bitLLM/bitnet_b1_58-3B-GGUF/resolve/main/bitnet_b1_58-3B-Q4_K_M.gguf",
|
|
32
|
+
"filename": "bitnet_b1_58-3B-Q4_K_M.gguf",
|
|
33
|
+
"size_desc": "~1.8 GB",
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
def cmd_setup() -> None:
|
|
38
|
+
"""Diagnoses environment and provisions required native tools."""
|
|
39
|
+
print("=" * 70)
|
|
40
|
+
print(f"⚡ termux-aichain v{__version__} One-Touch Environment Diagnostics")
|
|
41
|
+
print("=" * 70)
|
|
42
|
+
|
|
43
|
+
is_termux = "com.termux" in os.environ.get("PREFIX", "") or os.path.exists("/data/data/com.termux")
|
|
44
|
+
print(f"• Platform Environment : {'Android Termux (Native)' if is_termux else 'Host OS'}")
|
|
45
|
+
print(f"• Python Version : {sys.version.split()[0]}")
|
|
46
|
+
|
|
47
|
+
# Check Termux:API
|
|
48
|
+
has_api = bool(shutil.which("termux-battery-status"))
|
|
49
|
+
print(f"• Termux-API CLI Tools : {'[OK] Installed' if has_api else '[WARN] Not Installed (Using Kernel Sysfs Fallback)'}")
|
|
50
|
+
|
|
51
|
+
# Check llama-server
|
|
52
|
+
has_llama = bool(shutil.which("llama-server"))
|
|
53
|
+
print(f"• Local llama-server : {'[OK] Available' if has_llama else '[OPTIONAL] Not in PATH (Can use external/bitnet endpoint)'}")
|
|
54
|
+
|
|
55
|
+
# Check Node.js
|
|
56
|
+
has_node = bool(shutil.which("node"))
|
|
57
|
+
print(f"• Node.js ESM Runtime : {'[OK] ' + subprocess.check_output(['node', '-v'], text=True).strip() if has_node else '[INFO] Node.js not detected'}")
|
|
58
|
+
|
|
59
|
+
print("-" * 70)
|
|
60
|
+
if is_termux and not has_api:
|
|
61
|
+
print("[*] To enable full hardware sensors/vibration on Termux, run:")
|
|
62
|
+
print(" pkg update && pkg install termux-api -y\n")
|
|
63
|
+
print("[+] All Core, Graph, Memory, Serve, and Trace modules are 100% ready!")
|
|
64
|
+
print("=" * 70)
|
|
65
|
+
|
|
66
|
+
def cmd_pull(model_name: str) -> None:
|
|
67
|
+
"""Downloads validated lightweight model GGUF for local inference."""
|
|
68
|
+
target = model_name.lower().strip()
|
|
69
|
+
if target not in MODELS_REGISTRY:
|
|
70
|
+
print(f"[-] Unknown model '{model_name}'. Available options:")
|
|
71
|
+
for k, v in MODELS_REGISTRY.items():
|
|
72
|
+
print(f" - {k} ({v['size_desc']})")
|
|
73
|
+
return
|
|
74
|
+
|
|
75
|
+
info = MODELS_REGISTRY[target]
|
|
76
|
+
dest_dir = os.path.expanduser("~/models")
|
|
77
|
+
os.makedirs(dest_dir, exist_ok=True)
|
|
78
|
+
dest_file = os.path.join(dest_dir, info["filename"])
|
|
79
|
+
|
|
80
|
+
if os.path.exists(dest_file):
|
|
81
|
+
print(f"[*] Model already exists at: {dest_file}")
|
|
82
|
+
return
|
|
83
|
+
|
|
84
|
+
print(f"[*] Downloading {target} ({info['size_desc']}) to {dest_file}...")
|
|
85
|
+
try:
|
|
86
|
+
urllib.request.urlretrieve(info["url"], dest_file)
|
|
87
|
+
print(f"[+] Successfully downloaded: {dest_file}")
|
|
88
|
+
except Exception as ex:
|
|
89
|
+
print(f"[-] Download failed: {str(ex)}")
|
|
90
|
+
|
|
91
|
+
def cmd_serve(port: int, host: str) -> None:
|
|
92
|
+
"""Launches instant 1-line agent server with Live Web Dashboard."""
|
|
93
|
+
prompt = PromptTemplate.from_template("Edge Task: {input}")
|
|
94
|
+
chain = prompt | (lambda s: f"termux-aichain processed: {s}")
|
|
95
|
+
serve(chain, host=host, port=port, block=True)
|
|
96
|
+
|
|
97
|
+
def main() -> None:
|
|
98
|
+
parser = argparse.ArgumentParser(
|
|
99
|
+
prog="termux-aichain",
|
|
100
|
+
description="Sovereign Zero-Dependency AI Framework for Termux & Android Edge"
|
|
101
|
+
)
|
|
102
|
+
parser.add_argument("-v", "--version", action="version", version=f"%(prog)s {__version__}")
|
|
103
|
+
subparsers = parser.add_subparsers(dest="command", help="Available subcommands")
|
|
104
|
+
|
|
105
|
+
# setup
|
|
106
|
+
subparsers.add_parser("setup", help="Diagnose environment and check native tools")
|
|
107
|
+
|
|
108
|
+
# pull
|
|
109
|
+
pull_parser = subparsers.add_parser("pull", help="Download verified lightweight GGUF model")
|
|
110
|
+
pull_parser.add_argument("model", choices=list(MODELS_REGISTRY.keys()), help="Target model identifier")
|
|
111
|
+
|
|
112
|
+
# serve
|
|
113
|
+
serve_parser = subparsers.add_parser("serve", help="Launch 1-line REST/SSE/Web Dashboard server")
|
|
114
|
+
serve_parser.add_argument("--port", type=int, default=8080, help="Port to bind (default: 8080)")
|
|
115
|
+
serve_parser.add_argument("--host", type=str, default="0.0.0.0", help="Host to bind (default: 0.0.0.0)")
|
|
116
|
+
|
|
117
|
+
args = parser.parse_args()
|
|
118
|
+
if args.command == "setup":
|
|
119
|
+
cmd_setup()
|
|
120
|
+
elif args.command == "pull":
|
|
121
|
+
cmd_pull(args.model)
|
|
122
|
+
elif args.command == "serve":
|
|
123
|
+
cmd_serve(args.port, args.host)
|
|
124
|
+
else:
|
|
125
|
+
parser.print_help()
|
|
126
|
+
|
|
127
|
+
if __name__ == "__main__":
|
|
128
|
+
main()
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""
|
|
2
|
+
==============================================================================
|
|
3
|
+
termux-aichain Core Module Exports
|
|
4
|
+
==============================================================================
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from termux_aichain.core.schema import (
|
|
8
|
+
Message,
|
|
9
|
+
HumanMessage,
|
|
10
|
+
AIMessage,
|
|
11
|
+
SystemMessage,
|
|
12
|
+
ToolMessage,
|
|
13
|
+
UsageInfo,
|
|
14
|
+
GenerationResult,
|
|
15
|
+
StreamChunk,
|
|
16
|
+
)
|
|
17
|
+
from termux_aichain.core.prompt import (
|
|
18
|
+
PromptTemplate,
|
|
19
|
+
ChatPromptTemplate,
|
|
20
|
+
)
|
|
21
|
+
from termux_aichain.core.base import (
|
|
22
|
+
Runnable,
|
|
23
|
+
RunnableLambda,
|
|
24
|
+
RunnableSequence,
|
|
25
|
+
BaseChatModel,
|
|
26
|
+
)
|
|
27
|
+
from termux_aichain.core.providers.openai_compatible import OpenAICompatibleChat
|
|
28
|
+
from termux_aichain.core.providers.bitnet import BitNetChat
|
|
29
|
+
from termux_aichain.core.providers.local_server import (
|
|
30
|
+
LocalServerConfig,
|
|
31
|
+
LocalServerManager,
|
|
32
|
+
LlamaCppServer,
|
|
33
|
+
BitNetServer,
|
|
34
|
+
)
|
|
35
|
+
from termux_aichain.core.parsers import (
|
|
36
|
+
StringOutputParser,
|
|
37
|
+
JsonOutputParser,
|
|
38
|
+
RegexOutputParser,
|
|
39
|
+
)
|
|
40
|
+
from termux_aichain.core.splitters import (
|
|
41
|
+
Document,
|
|
42
|
+
CharacterTextSplitter,
|
|
43
|
+
RecursiveCharacterTextSplitter,
|
|
44
|
+
TextLoader,
|
|
45
|
+
MarkdownLoader,
|
|
46
|
+
JSONLoader,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
__all__ = [
|
|
50
|
+
"Message",
|
|
51
|
+
"HumanMessage",
|
|
52
|
+
"AIMessage",
|
|
53
|
+
"SystemMessage",
|
|
54
|
+
"ToolMessage",
|
|
55
|
+
"UsageInfo",
|
|
56
|
+
"GenerationResult",
|
|
57
|
+
"StreamChunk",
|
|
58
|
+
"PromptTemplate",
|
|
59
|
+
"ChatPromptTemplate",
|
|
60
|
+
"Runnable",
|
|
61
|
+
"RunnableLambda",
|
|
62
|
+
"RunnableSequence",
|
|
63
|
+
"BaseChatModel",
|
|
64
|
+
"OpenAICompatibleChat",
|
|
65
|
+
"BitNetChat",
|
|
66
|
+
"LocalServerConfig",
|
|
67
|
+
"LocalServerManager",
|
|
68
|
+
"LlamaCppServer",
|
|
69
|
+
"BitNetServer",
|
|
70
|
+
"StringOutputParser",
|
|
71
|
+
"JsonOutputParser",
|
|
72
|
+
"RegexOutputParser",
|
|
73
|
+
"Document",
|
|
74
|
+
"CharacterTextSplitter",
|
|
75
|
+
"RecursiveCharacterTextSplitter",
|
|
76
|
+
"TextLoader",
|
|
77
|
+
"MarkdownLoader",
|
|
78
|
+
"JSONLoader",
|
|
79
|
+
]
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"""
|
|
2
|
+
==============================================================================
|
|
3
|
+
termux-aichain Core Engine: Runnable Base & Pipe Composition (|)
|
|
4
|
+
==============================================================================
|
|
5
|
+
Provides standard Runnable, RunnableLambda, RunnableSequence interfaces.
|
|
6
|
+
Zero external heavy dependencies - Pure Python 3.10+ standard library.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
import inspect
|
|
11
|
+
import asyncio
|
|
12
|
+
from abc import ABC, abstractmethod
|
|
13
|
+
from typing import Any, AsyncIterator, Callable, Dict, Iterator, List, Optional, Union
|
|
14
|
+
from termux_aichain.core.schema import Message, HumanMessage, AIMessage, GenerationResult, StreamChunk
|
|
15
|
+
|
|
16
|
+
class Runnable(ABC):
|
|
17
|
+
"""Abstract Base Class for all executable chains, templates, and models."""
|
|
18
|
+
|
|
19
|
+
@abstractmethod
|
|
20
|
+
def invoke(self, input_data: Any, **kwargs: Any) -> Any:
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
async def ainvoke(self, input_data: Any, **kwargs: Any) -> Any:
|
|
24
|
+
return await asyncio.to_thread(self.invoke, input_data, **kwargs)
|
|
25
|
+
|
|
26
|
+
def stream(self, input_data: Any, **kwargs: Any) -> Iterator[Any]:
|
|
27
|
+
yield self.invoke(input_data, **kwargs)
|
|
28
|
+
|
|
29
|
+
async def astream(self, input_data: Any, **kwargs: Any) -> AsyncIterator[Any]:
|
|
30
|
+
yield await self.ainvoke(input_data, **kwargs)
|
|
31
|
+
|
|
32
|
+
def __or__(self, other: Union[Runnable, Callable[[Any], Any]]) -> RunnableSequence:
|
|
33
|
+
right = other if isinstance(other, Runnable) else RunnableLambda(other)
|
|
34
|
+
return RunnableSequence(self, right)
|
|
35
|
+
|
|
36
|
+
def __ror__(self, other: Union[Runnable, Callable[[Any], Any]]) -> RunnableSequence:
|
|
37
|
+
left = other if isinstance(other, Runnable) else RunnableLambda(other)
|
|
38
|
+
return RunnableSequence(left, self)
|
|
39
|
+
|
|
40
|
+
class RunnableLambda(Runnable):
|
|
41
|
+
"""Wraps any standard Python callable into a pipe-compatible Runnable."""
|
|
42
|
+
|
|
43
|
+
def __init__(self, func: Callable[[Any], Any]):
|
|
44
|
+
self.func = func
|
|
45
|
+
|
|
46
|
+
def invoke(self, input_data: Any, **kwargs: Any) -> Any:
|
|
47
|
+
if kwargs:
|
|
48
|
+
return self.func(input_data, **kwargs)
|
|
49
|
+
return self.func(input_data)
|
|
50
|
+
|
|
51
|
+
async def ainvoke(self, input_data: Any, **kwargs: Any) -> Any:
|
|
52
|
+
if inspect.iscoroutinefunction(self.func):
|
|
53
|
+
return await self.func(input_data, **kwargs)
|
|
54
|
+
return await asyncio.to_thread(self.invoke, input_data, **kwargs)
|
|
55
|
+
|
|
56
|
+
class RunnableSequence(Runnable):
|
|
57
|
+
"""Executes multiple Runnables sequentially in a linear pipe chain."""
|
|
58
|
+
|
|
59
|
+
def __init__(self, *steps: Runnable):
|
|
60
|
+
self.steps: List[Runnable] = []
|
|
61
|
+
for step in steps:
|
|
62
|
+
if isinstance(step, RunnableSequence):
|
|
63
|
+
self.steps.extend(step.steps)
|
|
64
|
+
elif isinstance(step, Runnable):
|
|
65
|
+
self.steps.append(step)
|
|
66
|
+
elif callable(step):
|
|
67
|
+
self.steps.append(RunnableLambda(step))
|
|
68
|
+
else:
|
|
69
|
+
raise TypeError(f"Invalid step in sequence: {type(step)}")
|
|
70
|
+
|
|
71
|
+
def invoke(self, input_data: Any, **kwargs: Any) -> Any:
|
|
72
|
+
current = input_data
|
|
73
|
+
for step in self.steps:
|
|
74
|
+
current = step.invoke(current, **kwargs)
|
|
75
|
+
return current
|
|
76
|
+
|
|
77
|
+
async def ainvoke(self, input_data: Any, **kwargs: Any) -> Any:
|
|
78
|
+
current = input_data
|
|
79
|
+
for step in self.steps:
|
|
80
|
+
current = await step.ainvoke(current, **kwargs)
|
|
81
|
+
return current
|
|
82
|
+
|
|
83
|
+
def stream(self, input_data: Any, **kwargs: Any) -> Iterator[Any]:
|
|
84
|
+
if not self.steps:
|
|
85
|
+
return
|
|
86
|
+
if len(self.steps) == 1:
|
|
87
|
+
yield from self.steps[0].stream(input_data, **kwargs)
|
|
88
|
+
return
|
|
89
|
+
|
|
90
|
+
current = input_data
|
|
91
|
+
for step in self.steps[:-1]:
|
|
92
|
+
current = step.invoke(current, **kwargs)
|
|
93
|
+
|
|
94
|
+
yield from self.steps[-1].stream(current, **kwargs)
|
|
95
|
+
|
|
96
|
+
async def astream(self, input_data: Any, **kwargs: Any) -> AsyncIterator[Any]:
|
|
97
|
+
if not self.steps:
|
|
98
|
+
return
|
|
99
|
+
if len(self.steps) == 1:
|
|
100
|
+
async for chunk in self.steps[0].astream(input_data, **kwargs):
|
|
101
|
+
yield chunk
|
|
102
|
+
return
|
|
103
|
+
|
|
104
|
+
current = input_data
|
|
105
|
+
for step in self.steps[:-1]:
|
|
106
|
+
current = await step.ainvoke(current, **kwargs)
|
|
107
|
+
|
|
108
|
+
async for chunk in self.steps[-1].astream(current, **kwargs):
|
|
109
|
+
yield chunk
|
|
110
|
+
|
|
111
|
+
def __or__(self, other: Union[Runnable, Callable[[Any], Any]]) -> RunnableSequence:
|
|
112
|
+
right = other if isinstance(other, Runnable) else RunnableLambda(other)
|
|
113
|
+
return RunnableSequence(*self.steps, right)
|
|
114
|
+
|
|
115
|
+
class BaseChatModel(Runnable, ABC):
|
|
116
|
+
"""Abstract Base Class for Chat Models."""
|
|
117
|
+
|
|
118
|
+
@abstractmethod
|
|
119
|
+
def generate(self, messages: List[Message]) -> GenerationResult:
|
|
120
|
+
pass
|
|
121
|
+
|
|
122
|
+
async def agenerate(self, messages: List[Message]) -> GenerationResult:
|
|
123
|
+
return await asyncio.to_thread(self.generate, messages)
|
|
124
|
+
|
|
125
|
+
def invoke(self, input_data: Union[str, List[Message], Dict[str, Any]], **kwargs: Any) -> GenerationResult:
|
|
126
|
+
messages = self._coerce_messages(input_data)
|
|
127
|
+
return self.generate(messages)
|
|
128
|
+
|
|
129
|
+
async def ainvoke(self, input_data: Union[str, List[Message], Dict[str, Any]], **kwargs: Any) -> GenerationResult:
|
|
130
|
+
messages = self._coerce_messages(input_data)
|
|
131
|
+
return await self.agenerate(messages)
|
|
132
|
+
|
|
133
|
+
def _coerce_messages(self, input_data: Union[str, List[Message], Dict[str, Any]]) -> List[Message]:
|
|
134
|
+
if isinstance(input_data, str):
|
|
135
|
+
return [HumanMessage(content=input_data)]
|
|
136
|
+
elif isinstance(input_data, list):
|
|
137
|
+
return input_data
|
|
138
|
+
elif isinstance(input_data, dict):
|
|
139
|
+
if "messages" in input_data:
|
|
140
|
+
return input_data["messages"]
|
|
141
|
+
elif "input" in input_data:
|
|
142
|
+
return [HumanMessage(content=str(input_data["input"]))]
|
|
143
|
+
return [HumanMessage(content=str(input_data))]
|
|
144
|
+
return [HumanMessage(content=str(input_data))]
|