neobot-chat 1.0.0a7__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.
Files changed (38) hide show
  1. neobot_chat-1.0.0a7/PKG-INFO +11 -0
  2. neobot_chat-1.0.0a7/README.md +0 -0
  3. neobot_chat-1.0.0a7/pyproject.toml +21 -0
  4. neobot_chat-1.0.0a7/src/neobot_chat/__init__.py +72 -0
  5. neobot_chat-1.0.0a7/src/neobot_chat/graph/__init__.py +6 -0
  6. neobot_chat-1.0.0a7/src/neobot_chat/graph/constants.py +1 -0
  7. neobot_chat-1.0.0a7/src/neobot_chat/graph/executor.py +53 -0
  8. neobot_chat-1.0.0a7/src/neobot_chat/graph/graph.py +60 -0
  9. neobot_chat-1.0.0a7/src/neobot_chat/graph/nodes.py +22 -0
  10. neobot_chat-1.0.0a7/src/neobot_chat/graph/types.py +8 -0
  11. neobot_chat-1.0.0a7/src/neobot_chat/models.py +161 -0
  12. neobot_chat-1.0.0a7/src/neobot_chat/providers/__init__.py +6 -0
  13. neobot_chat-1.0.0a7/src/neobot_chat/providers/anthropic.py +260 -0
  14. neobot_chat-1.0.0a7/src/neobot_chat/providers/base.py +93 -0
  15. neobot_chat-1.0.0a7/src/neobot_chat/providers/deepseek_offical.py +399 -0
  16. neobot_chat-1.0.0a7/src/neobot_chat/providers/openai.py +192 -0
  17. neobot_chat-1.0.0a7/src/neobot_chat/py.typed +0 -0
  18. neobot_chat-1.0.0a7/src/neobot_chat/runtime/__init__.py +4 -0
  19. neobot_chat-1.0.0a7/src/neobot_chat/runtime/agent.py +301 -0
  20. neobot_chat-1.0.0a7/src/neobot_chat/runtime/prompt.py +164 -0
  21. neobot_chat-1.0.0a7/src/neobot_chat/runtime/workflow.py +53 -0
  22. neobot_chat-1.0.0a7/src/neobot_chat/schema/__init__.py +55 -0
  23. neobot_chat-1.0.0a7/src/neobot_chat/schema/exceptions.py +21 -0
  24. neobot_chat-1.0.0a7/src/neobot_chat/schema/protocol.py +40 -0
  25. neobot_chat-1.0.0a7/src/neobot_chat/schema/types.py +92 -0
  26. neobot_chat-1.0.0a7/src/neobot_chat/skills/__init__.py +4 -0
  27. neobot_chat-1.0.0a7/src/neobot_chat/skills/inject.py +38 -0
  28. neobot_chat-1.0.0a7/src/neobot_chat/skills/registry.py +121 -0
  29. neobot_chat-1.0.0a7/src/neobot_chat/tools/__init__.py +15 -0
  30. neobot_chat-1.0.0a7/src/neobot_chat/tools/builtin.py +300 -0
  31. neobot_chat-1.0.0a7/src/neobot_chat/tools/composite.py +45 -0
  32. neobot_chat-1.0.0a7/src/neobot_chat/tools/registry.py +144 -0
  33. neobot_chat-1.0.0a7/src/neobot_chat/tools/shell.py +85 -0
  34. neobot_chat-1.0.0a7/src/neobot_chat/tools/toolset.py +92 -0
  35. neobot_chat-1.0.0a7/src/neobot_chat/utils/__init__.py +4 -0
  36. neobot_chat-1.0.0a7/src/neobot_chat/utils/preprocessors.py +21 -0
  37. neobot_chat-1.0.0a7/src/neobot_chat/utils/tools.py +9 -0
  38. neobot_chat-1.0.0a7/src/neobot_chat/utils/xml.py +122 -0
@@ -0,0 +1,11 @@
1
+ Metadata-Version: 2.3
2
+ Name: neobot-chat
3
+ Version: 1.0.0a7
4
+ Summary: Add your description here
5
+ Author: wsrsq, tangtian
6
+ Author-email: wsrsq <wsrsq001@163.com>, tangtian <a14b@126.com>
7
+ Requires-Dist: httpx>=0.27.0
8
+ Requires-Dist: neobot-contracts
9
+ Requires-Python: >=3.13
10
+ Description-Content-Type: text/markdown
11
+
File without changes
@@ -0,0 +1,21 @@
1
+ [project]
2
+ name = "neobot-chat"
3
+ version = "1.0.0-alpha.7"
4
+ description = "Add your description here"
5
+ readme = "README.md"
6
+ authors = [
7
+ { name = "wsrsq", email = "wsrsq001@163.com" },
8
+ { name = "tangtian", email = "a14b@126.com" },
9
+ ]
10
+ requires-python = ">=3.13"
11
+ dependencies = [
12
+ "httpx>=0.27.0",
13
+ "neobot-contracts",
14
+ ]
15
+
16
+ [tool.uv.sources]
17
+ neobot-contracts = { workspace = true }
18
+
19
+ [build-system]
20
+ requires = ["uv_build>=0.9.27,<0.10.0"]
21
+ build-backend = "uv_build"
@@ -0,0 +1,72 @@
1
+ from neobot_chat.runtime.agent import Agent
2
+ from neobot_chat.graph import END, CompiledGraph, StateGraph, skill_node
3
+ from neobot_chat.schema.protocol import (
4
+ AgentLike,
5
+ ChatService,
6
+ Runnable,
7
+ StatePreprocessor,
8
+ StreamableRunnable,
9
+ ToolExecutor,
10
+ )
11
+ from neobot_chat.skills import SkillRegistry, build_skill_preprocessor, inject_skills
12
+ from neobot_chat.tools import AgentRegistry, BuiltinTools, CompositeToolExecutor, Toolset, build_builtin_toolset
13
+ from neobot_chat.models import (
14
+ ModelPricing,
15
+ ModelRegistry,
16
+ ModelSettings,
17
+ RegisteredModel,
18
+ create_provider,
19
+ get_model_registry,
20
+ get_registered_model,
21
+ register_model,
22
+ )
23
+ from neobot_chat.schema.types import (
24
+ ChatChunk,
25
+ MessageContent,
26
+ OnEvent,
27
+ State,
28
+ ToolAccessPolicy,
29
+ ToolAccessRule,
30
+ ToolGuardContext,
31
+ )
32
+ from neobot_chat.utils import compose_preprocessors, parse_tool_args
33
+ from neobot_chat.runtime.workflow import Workflow
34
+
35
+ __all__ = [
36
+ "Agent",
37
+ "AgentRegistry",
38
+ "AgentLike",
39
+ "BuiltinTools",
40
+ "build_builtin_toolset",
41
+ "CompositeToolExecutor",
42
+ "ChatService",
43
+ "ChatChunk",
44
+ "MessageContent",
45
+ "ModelPricing",
46
+ "ModelRegistry",
47
+ "ModelSettings",
48
+ "RegisteredModel",
49
+ "CompiledGraph",
50
+ "compose_preprocessors",
51
+ "create_provider",
52
+ "END",
53
+ "build_skill_preprocessor",
54
+ "get_model_registry",
55
+ "get_registered_model",
56
+ "inject_skills",
57
+ "OnEvent",
58
+ "parse_tool_args",
59
+ "register_model",
60
+ "Runnable",
61
+ "StatePreprocessor",
62
+ "skill_node",
63
+ "SkillRegistry",
64
+ "State",
65
+ "StateGraph",
66
+ "ToolAccessPolicy",
67
+ "ToolAccessRule",
68
+ "ToolGuardContext",
69
+ "StreamableRunnable",
70
+ "ToolExecutor",
71
+ "Workflow",
72
+ ]
@@ -0,0 +1,6 @@
1
+ from neobot_chat.graph.constants import END
2
+ from neobot_chat.graph.executor import CompiledGraph
3
+ from neobot_chat.graph.graph import StateGraph
4
+ from neobot_chat.graph.nodes import skill_node
5
+
6
+ __all__ = ["END", "CompiledGraph", "StateGraph", "skill_node"]
@@ -0,0 +1 @@
1
+ END = "__end__"
@@ -0,0 +1,53 @@
1
+ from __future__ import annotations
2
+
3
+ from neobot_chat.schema.exceptions import GraphError
4
+ from neobot_chat.graph.constants import END
5
+ from neobot_chat.graph.types import StateCondition, StateNode
6
+ from neobot_chat.schema.types import State
7
+
8
+
9
+ class CompiledGraph:
10
+ """编译后的可执行图"""
11
+
12
+ def __init__(
13
+ self,
14
+ nodes: dict[str, StateNode],
15
+ edges: dict[str, str],
16
+ conditional_edges: dict[str, tuple[StateCondition, dict[str, str]]],
17
+ entry_point: str,
18
+ max_steps: int = 100,
19
+ ):
20
+ self._nodes = nodes
21
+ self._edges = edges
22
+ self._conditional_edges = conditional_edges
23
+ self._entry_point = entry_point
24
+ self._max_steps = max_steps
25
+
26
+ def _resolve_next(self, current: str, state: State) -> str | None:
27
+ if current in self._conditional_edges:
28
+ condition, mapping = self._conditional_edges[current]
29
+ key = condition(state)
30
+ if key not in mapping:
31
+ raise GraphError(
32
+ f"Condition on '{current}' returned unmapped key '{key}', "
33
+ f"expected one of {list(mapping)}"
34
+ )
35
+ return mapping[key]
36
+ return self._edges.get(current)
37
+
38
+ async def invoke(self, state: State) -> State:
39
+ current: str | None = self._entry_point
40
+
41
+ for _ in range(self._max_steps):
42
+ if current is None or current == END:
43
+ break
44
+ if current not in self._nodes:
45
+ raise GraphError(f"Unknown node '{current}'")
46
+ state = await self._nodes[current](state)
47
+ current = self._resolve_next(current, state)
48
+ else:
49
+ raise GraphError(
50
+ f"Graph exceeded max steps ({self._max_steps}), possible infinite loop"
51
+ )
52
+
53
+ return state
@@ -0,0 +1,60 @@
1
+ from __future__ import annotations
2
+
3
+ from neobot_chat.schema.exceptions import GraphError
4
+ from neobot_chat.graph.constants import END
5
+ from neobot_chat.graph.executor import CompiledGraph
6
+ from neobot_chat.graph.types import StateCondition, StateNode
7
+
8
+
9
+ class StateGraph:
10
+ """状态图构建器"""
11
+
12
+ def __init__(self):
13
+ self._nodes: dict[str, StateNode] = {}
14
+ self._edges: dict[str, str] = {}
15
+ self._conditional_edges: dict[str, tuple[StateCondition, dict[str, str]]] = {}
16
+ self._entry_point: str | None = None
17
+
18
+ def _assert_no_outgoing(self, node: str) -> None:
19
+ if node in self._edges or node in self._conditional_edges:
20
+ raise GraphError(f"Node '{node}' already has outgoing edges")
21
+
22
+ def add_node(self, name: str, func: StateNode) -> None:
23
+ if name in self._nodes:
24
+ raise GraphError(f"Node '{name}' already exists")
25
+ self._nodes[name] = func
26
+
27
+ def add_edge(self, from_node: str, to_node: str) -> None:
28
+ self._assert_no_outgoing(from_node)
29
+ self._edges[from_node] = to_node
30
+
31
+ def add_conditional_edges(
32
+ self, from_node: str, condition: StateCondition, mapping: dict[str, str]
33
+ ) -> None:
34
+ self._assert_no_outgoing(from_node)
35
+ self._conditional_edges[from_node] = (condition, mapping)
36
+
37
+ def set_entry_point(self, node: str) -> None:
38
+ self._entry_point = node
39
+
40
+ def compile(self) -> CompiledGraph:
41
+ if self._entry_point is None:
42
+ raise GraphError("Entry point not set")
43
+ if self._entry_point not in self._nodes:
44
+ raise GraphError(f"Entry point '{self._entry_point}' not found in nodes")
45
+ valid = set(self._nodes) | {END}
46
+ for src, dst in self._edges.items():
47
+ if dst not in valid:
48
+ raise GraphError(f"Edge '{src}' -> '{dst}': target node not found")
49
+ for src, (_, mapping) in self._conditional_edges.items():
50
+ for dst in mapping.values():
51
+ if dst not in valid:
52
+ raise GraphError(
53
+ f"Conditional edge '{src}' -> '{dst}': target node not found"
54
+ )
55
+ return CompiledGraph(
56
+ nodes=self._nodes,
57
+ edges=self._edges,
58
+ conditional_edges=self._conditional_edges,
59
+ entry_point=self._entry_point,
60
+ )
@@ -0,0 +1,22 @@
1
+ from __future__ import annotations
2
+
3
+ from neobot_chat.graph.types import StateNode
4
+ from neobot_chat.skills.inject import inject_skills
5
+ from neobot_chat.skills.registry import SkillRegistry
6
+ from neobot_chat.schema.types import State
7
+
8
+
9
+ def skill_node(skills: SkillRegistry) -> StateNode:
10
+ """创建一个 Graph 内置节点,自动匹配 skills 并注入 system prompt
11
+
12
+ 用法::
13
+
14
+ graph.add_node("skills", skill_node(registry))
15
+ graph.add_edge("skills", "agent")
16
+ graph.set_entry_point("skills")
17
+ """
18
+
19
+ async def _node(state: State) -> State:
20
+ return inject_skills(skills, state)
21
+
22
+ return _node
@@ -0,0 +1,8 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Awaitable, Callable
4
+
5
+ from neobot_chat.schema.types import State
6
+
7
+ StateNode = Callable[[State], Awaitable[State]]
8
+ StateCondition = Callable[[State], str]
@@ -0,0 +1,161 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from typing import Any
5
+
6
+ from neobot_chat.providers import (
7
+ AnthropicProvider,
8
+ DeepSeekOfficialProvider,
9
+ OpenAIProvider,
10
+ Provider,
11
+ )
12
+ from neobot_chat.schema.exceptions import ValidationError
13
+
14
+
15
+ @dataclass(frozen=True)
16
+ class ModelPricing:
17
+ """模型价格信息。"""
18
+
19
+ input_price_per_mtokens: float = 0.0
20
+ output_price_per_mtokens: float = 0.0
21
+ cache_hit_price_per_mtokens: float = 0.0
22
+ billing_metric: str = ""
23
+
24
+
25
+ @dataclass(frozen=True)
26
+ class ModelSettings:
27
+ """模型运行设置。"""
28
+
29
+ temperature: float | None = None
30
+ max_output_tokens: int | None = None
31
+ timeout_seconds: float = 120.0
32
+ top_p: float | None = None
33
+ frequency_penalty: float | None = None
34
+ presence_penalty: float | None = None
35
+ extra_body: dict[str, Any] = field(default_factory=dict)
36
+
37
+
38
+ def _normalize_provider_kind(provider_name: str) -> str:
39
+ normalized = provider_name.strip().casefold().replace("-", "_")
40
+ if normalized in {"anthropic"}:
41
+ return "anthropic"
42
+ if normalized in {"deepseek", "deepseek_offical", "deepseek_official"}:
43
+ return "deepseek"
44
+ if normalized in {"openai"}:
45
+ return "openai"
46
+ # 默认按 OpenAI 兼容接口处理,便于接入自定义平台代理。
47
+ return "openai"
48
+
49
+
50
+ @dataclass(frozen=True)
51
+ class RegisteredModel:
52
+ """已注册模型。"""
53
+
54
+ name: str
55
+ description: str
56
+ provider_name: str
57
+ model_name: str
58
+ base_url: str
59
+ api_key: str
60
+ pricing: ModelPricing = field(default_factory=ModelPricing)
61
+ settings: ModelSettings = field(default_factory=ModelSettings)
62
+
63
+ @property
64
+ def provider_kind(self) -> str:
65
+ return _normalize_provider_kind(self.provider_name)
66
+
67
+ def create_provider(self) -> Provider:
68
+ """根据注册信息创建 Provider 实例。"""
69
+ if not self.base_url:
70
+ raise ValidationError(f"Model '{self.name}' is missing base_url")
71
+ if not self.api_key:
72
+ raise ValidationError(f"Model '{self.name}' is missing api_key")
73
+
74
+ if self.provider_kind == "anthropic":
75
+ return AnthropicProvider(
76
+ api_key=self.api_key,
77
+ model=self.model_name,
78
+ base_url=self.base_url,
79
+ max_tokens=self.settings.max_output_tokens or 4096,
80
+ timeout=self.settings.timeout_seconds,
81
+ temperature=self.settings.temperature,
82
+ top_p=self.settings.top_p,
83
+ extra_body=self.settings.extra_body,
84
+ )
85
+
86
+ if self.provider_kind == "deepseek":
87
+ return DeepSeekOfficialProvider(
88
+ api_key=self.api_key,
89
+ model=self.model_name,
90
+ base_url=self.base_url,
91
+ timeout=self.settings.timeout_seconds,
92
+ temperature=self.settings.temperature,
93
+ max_tokens=self.settings.max_output_tokens,
94
+ top_p=self.settings.top_p,
95
+ frequency_penalty=self.settings.frequency_penalty,
96
+ presence_penalty=self.settings.presence_penalty,
97
+ extra_body=self.settings.extra_body,
98
+ )
99
+
100
+ return OpenAIProvider(
101
+ api_key=self.api_key,
102
+ model=self.model_name,
103
+ base_url=self.base_url,
104
+ timeout=self.settings.timeout_seconds,
105
+ temperature=self.settings.temperature,
106
+ max_tokens=self.settings.max_output_tokens,
107
+ top_p=self.settings.top_p,
108
+ frequency_penalty=self.settings.frequency_penalty,
109
+ presence_penalty=self.settings.presence_penalty,
110
+ extra_body=self.settings.extra_body,
111
+ )
112
+
113
+
114
+ class ModelRegistry:
115
+ """模型注册中心。"""
116
+
117
+ def __init__(self) -> None:
118
+ self._models: dict[str, RegisteredModel] = {}
119
+
120
+ @property
121
+ def names(self) -> tuple[str, ...]:
122
+ return tuple(self._models.keys())
123
+
124
+ def clear(self) -> None:
125
+ self._models.clear()
126
+
127
+ def register(self, model: RegisteredModel, *, replace: bool = True) -> None:
128
+ if not replace and model.name in self._models:
129
+ raise ValidationError(f"Model '{model.name}' is already registered")
130
+ self._models[model.name] = model
131
+
132
+ def get(self, name: str) -> RegisteredModel:
133
+ try:
134
+ return self._models[name]
135
+ except KeyError as exc:
136
+ raise ValidationError(f"Model '{name}' is not registered") from exc
137
+
138
+ def create_provider(self, name: str) -> Provider:
139
+ return self.get(name).create_provider()
140
+
141
+ def items(self) -> tuple[tuple[str, RegisteredModel], ...]:
142
+ return tuple(self._models.items())
143
+
144
+
145
+ model_registry = ModelRegistry()
146
+
147
+
148
+ def get_model_registry() -> ModelRegistry:
149
+ return model_registry
150
+
151
+
152
+ def register_model(model: RegisteredModel, *, replace: bool = True) -> None:
153
+ model_registry.register(model, replace=replace)
154
+
155
+
156
+ def get_registered_model(name: str) -> RegisteredModel:
157
+ return model_registry.get(name)
158
+
159
+
160
+ def create_provider(name: str) -> Provider:
161
+ return model_registry.create_provider(name)
@@ -0,0 +1,6 @@
1
+ from neobot_chat.providers.anthropic import AnthropicProvider
2
+ from neobot_chat.providers.base import Provider
3
+ from neobot_chat.providers.deepseek_offical import DeepSeekOfficalProvider, DeepSeekOfficialProvider
4
+ from neobot_chat.providers.openai import OpenAIProvider
5
+
6
+ __all__ = ["AnthropicProvider", "DeepSeekOfficalProvider", "DeepSeekOfficialProvider", "OpenAIProvider", "Provider"]