codegnipy 0.0.1__py3-none-any.whl
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.
- codegnipy/__init__.py +190 -0
- codegnipy/cli.py +153 -0
- codegnipy/decorator.py +151 -0
- codegnipy/determinism.py +631 -0
- codegnipy/memory.py +276 -0
- codegnipy/providers.py +1160 -0
- codegnipy/reflection.py +244 -0
- codegnipy/runtime.py +197 -0
- codegnipy/scheduler.py +498 -0
- codegnipy/streaming.py +387 -0
- codegnipy/tools.py +481 -0
- codegnipy/transformer.py +155 -0
- codegnipy/validation.py +961 -0
- codegnipy-0.0.1.dist-info/METADATA +417 -0
- codegnipy-0.0.1.dist-info/RECORD +19 -0
- codegnipy-0.0.1.dist-info/WHEEL +5 -0
- codegnipy-0.0.1.dist-info/entry_points.txt +2 -0
- codegnipy-0.0.1.dist-info/licenses/LICENSE +21 -0
- codegnipy-0.0.1.dist-info/top_level.txt +1 -0
codegnipy/tools.py
ADDED
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Codegnipy 工具调用模块
|
|
3
|
+
|
|
4
|
+
提供 Function Calling / Tool Use 支持,让 LLM 能够调用外部函数。
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import inspect
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from typing import (
|
|
11
|
+
Any, Callable, Dict, List, Optional, Type, Union,
|
|
12
|
+
get_type_hints, get_origin, get_args
|
|
13
|
+
)
|
|
14
|
+
from enum import Enum
|
|
15
|
+
from functools import wraps
|
|
16
|
+
|
|
17
|
+
from .runtime import LLMConfig, CognitiveContext
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ToolType(Enum):
|
|
21
|
+
"""工具类型"""
|
|
22
|
+
FUNCTION = "function"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class ToolParameter:
|
|
27
|
+
"""工具参数定义"""
|
|
28
|
+
name: str
|
|
29
|
+
param_type: str
|
|
30
|
+
description: str = ""
|
|
31
|
+
required: bool = True
|
|
32
|
+
enum: Optional[List[str]] = None
|
|
33
|
+
default: Any = None
|
|
34
|
+
|
|
35
|
+
def to_json_schema(self) -> dict:
|
|
36
|
+
"""转换为 JSON Schema"""
|
|
37
|
+
schema: Dict[str, Any] = {
|
|
38
|
+
"type": self.param_type,
|
|
39
|
+
"description": self.description
|
|
40
|
+
}
|
|
41
|
+
if self.enum:
|
|
42
|
+
schema["enum"] = self.enum
|
|
43
|
+
return schema
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass
|
|
47
|
+
class ToolDefinition:
|
|
48
|
+
"""工具定义"""
|
|
49
|
+
name: str
|
|
50
|
+
description: str
|
|
51
|
+
parameters: List[ToolParameter]
|
|
52
|
+
type: ToolType = ToolType.FUNCTION
|
|
53
|
+
handler: Optional[Callable] = None
|
|
54
|
+
|
|
55
|
+
def to_openai_tool(self) -> dict:
|
|
56
|
+
"""转换为 OpenAI 工具格式"""
|
|
57
|
+
properties = {}
|
|
58
|
+
required = []
|
|
59
|
+
|
|
60
|
+
for param in self.parameters:
|
|
61
|
+
properties[param.name] = param.to_json_schema()
|
|
62
|
+
if param.required:
|
|
63
|
+
required.append(param.name)
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
"type": self.type.value,
|
|
67
|
+
"function": {
|
|
68
|
+
"name": self.name,
|
|
69
|
+
"description": self.description,
|
|
70
|
+
"parameters": {
|
|
71
|
+
"type": "object",
|
|
72
|
+
"properties": properties,
|
|
73
|
+
"required": required
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@dataclass
|
|
80
|
+
class ToolCall:
|
|
81
|
+
"""工具调用请求"""
|
|
82
|
+
id: str
|
|
83
|
+
name: str
|
|
84
|
+
arguments: dict
|
|
85
|
+
|
|
86
|
+
def execute(self, handler: Callable) -> Any:
|
|
87
|
+
"""执行工具调用"""
|
|
88
|
+
return handler(**self.arguments)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@dataclass
|
|
92
|
+
class ToolResult:
|
|
93
|
+
"""工具调用结果"""
|
|
94
|
+
tool_call_id: str
|
|
95
|
+
name: str
|
|
96
|
+
arguments: dict
|
|
97
|
+
result: Any
|
|
98
|
+
error: Optional[str] = None
|
|
99
|
+
|
|
100
|
+
def to_openai_format(self) -> dict:
|
|
101
|
+
"""转换为 OpenAI 格式"""
|
|
102
|
+
content = json.dumps(self.result) if self.result else self.error
|
|
103
|
+
return {
|
|
104
|
+
"role": "tool",
|
|
105
|
+
"tool_call_id": self.tool_call_id,
|
|
106
|
+
"name": self.name,
|
|
107
|
+
"content": content
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class ToolRegistry:
|
|
112
|
+
"""工具注册表"""
|
|
113
|
+
|
|
114
|
+
def __init__(self):
|
|
115
|
+
self._tools: Dict[str, ToolDefinition] = {}
|
|
116
|
+
|
|
117
|
+
def register(
|
|
118
|
+
self,
|
|
119
|
+
func: Optional[Callable] = None,
|
|
120
|
+
*,
|
|
121
|
+
name: Optional[str] = None,
|
|
122
|
+
description: str = ""
|
|
123
|
+
) -> Callable:
|
|
124
|
+
"""
|
|
125
|
+
装饰器:注册工具函数
|
|
126
|
+
|
|
127
|
+
支持 @registry.register 和 @registry.register() 两种用法。
|
|
128
|
+
|
|
129
|
+
示例:
|
|
130
|
+
@registry.register
|
|
131
|
+
def get_weather(city: str) -> str:
|
|
132
|
+
return f"{city}的天气晴朗"
|
|
133
|
+
|
|
134
|
+
@registry.register(description="获取当前天气")
|
|
135
|
+
def get_weather2(city: str) -> str:
|
|
136
|
+
return f"{city}的天气晴朗"
|
|
137
|
+
"""
|
|
138
|
+
def decorator(fn: Callable) -> Callable:
|
|
139
|
+
tool_name = name or fn.__name__
|
|
140
|
+
tool_desc = description or fn.__doc__ or f"执行 {tool_name}"
|
|
141
|
+
|
|
142
|
+
# 从函数签名推断参数
|
|
143
|
+
sig = inspect.signature(fn)
|
|
144
|
+
hints = get_type_hints(fn) if hasattr(fn, '__annotations__') else {}
|
|
145
|
+
|
|
146
|
+
parameters = []
|
|
147
|
+
for param_name, param in sig.parameters.items():
|
|
148
|
+
if param_name == 'self':
|
|
149
|
+
continue
|
|
150
|
+
|
|
151
|
+
param_type = self._python_type_to_json(hints.get(param_name, str))
|
|
152
|
+
required = param.default is inspect.Parameter.empty
|
|
153
|
+
default = None if required else param.default
|
|
154
|
+
|
|
155
|
+
parameters.append(ToolParameter(
|
|
156
|
+
name=param_name,
|
|
157
|
+
param_type=param_type,
|
|
158
|
+
description=f"参数 {param_name}",
|
|
159
|
+
required=required,
|
|
160
|
+
default=default
|
|
161
|
+
))
|
|
162
|
+
|
|
163
|
+
tool = ToolDefinition(
|
|
164
|
+
name=tool_name,
|
|
165
|
+
description=tool_desc,
|
|
166
|
+
parameters=parameters,
|
|
167
|
+
handler=fn
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
self._tools[tool_name] = tool
|
|
171
|
+
|
|
172
|
+
@wraps(fn)
|
|
173
|
+
def wrapper(*args, **kwargs):
|
|
174
|
+
return fn(*args, **kwargs)
|
|
175
|
+
|
|
176
|
+
wrapper._tool_definition = tool # type: ignore[attr-defined]
|
|
177
|
+
|
|
178
|
+
return wrapper
|
|
179
|
+
|
|
180
|
+
# 支持 @registry.register 和 @registry.register() 两种用法
|
|
181
|
+
if func is not None:
|
|
182
|
+
return decorator(func)
|
|
183
|
+
return decorator
|
|
184
|
+
|
|
185
|
+
def add_tool(self, tool: ToolDefinition) -> None:
|
|
186
|
+
"""添加工具定义"""
|
|
187
|
+
self._tools[tool.name] = tool
|
|
188
|
+
|
|
189
|
+
def get_tool(self, name: str) -> Optional[ToolDefinition]:
|
|
190
|
+
"""获取工具定义"""
|
|
191
|
+
return self._tools.get(name)
|
|
192
|
+
|
|
193
|
+
def get_all_tools(self) -> List[ToolDefinition]:
|
|
194
|
+
"""获取所有工具"""
|
|
195
|
+
return list(self._tools.values())
|
|
196
|
+
|
|
197
|
+
def get_openai_tools(self) -> List[dict]:
|
|
198
|
+
"""获取 OpenAI 格式的工具列表"""
|
|
199
|
+
return [tool.to_openai_tool() for tool in self._tools.values()]
|
|
200
|
+
|
|
201
|
+
def execute(self, tool_call: ToolCall) -> ToolResult:
|
|
202
|
+
"""执行工具调用"""
|
|
203
|
+
tool = self.get_tool(tool_call.name)
|
|
204
|
+
|
|
205
|
+
if tool is None:
|
|
206
|
+
return ToolResult(
|
|
207
|
+
tool_call_id=tool_call.id,
|
|
208
|
+
name=tool_call.name,
|
|
209
|
+
arguments=tool_call.arguments,
|
|
210
|
+
result=None,
|
|
211
|
+
error=f"Unknown tool: {tool_call.name}"
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
if tool.handler is None:
|
|
215
|
+
return ToolResult(
|
|
216
|
+
tool_call_id=tool_call.id,
|
|
217
|
+
name=tool_call.name,
|
|
218
|
+
arguments=tool_call.arguments,
|
|
219
|
+
result=None,
|
|
220
|
+
error=f"No handler for tool: {tool_call.name}"
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
try:
|
|
224
|
+
result = tool.handler(**tool_call.arguments)
|
|
225
|
+
return ToolResult(
|
|
226
|
+
tool_call_id=tool_call.id,
|
|
227
|
+
name=tool_call.name,
|
|
228
|
+
arguments=tool_call.arguments,
|
|
229
|
+
result=result
|
|
230
|
+
)
|
|
231
|
+
except Exception as e:
|
|
232
|
+
return ToolResult(
|
|
233
|
+
tool_call_id=tool_call.id,
|
|
234
|
+
name=tool_call.name,
|
|
235
|
+
arguments=tool_call.arguments,
|
|
236
|
+
result=None,
|
|
237
|
+
error=str(e)
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
@staticmethod
|
|
241
|
+
def _python_type_to_json(python_type: Type) -> str:
|
|
242
|
+
"""将 Python 类型转换为 JSON Schema 类型"""
|
|
243
|
+
type_map = {
|
|
244
|
+
str: "string",
|
|
245
|
+
int: "integer",
|
|
246
|
+
float: "number",
|
|
247
|
+
bool: "boolean",
|
|
248
|
+
list: "array",
|
|
249
|
+
dict: "object",
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
# 处理 Optional 类型
|
|
253
|
+
origin = get_origin(python_type)
|
|
254
|
+
if origin is Union:
|
|
255
|
+
args = get_args(python_type)
|
|
256
|
+
# Optional[X] 实际上是 Union[X, None]
|
|
257
|
+
non_none_args = [a for a in args if a is not type(None)]
|
|
258
|
+
if non_none_args:
|
|
259
|
+
return ToolRegistry._python_type_to_json(non_none_args[0])
|
|
260
|
+
|
|
261
|
+
if origin is list:
|
|
262
|
+
return "array"
|
|
263
|
+
if origin is dict:
|
|
264
|
+
return "object"
|
|
265
|
+
|
|
266
|
+
return type_map.get(python_type, "string")
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def tool(
|
|
270
|
+
name: Optional[str] = None,
|
|
271
|
+
description: str = ""
|
|
272
|
+
) -> Callable:
|
|
273
|
+
"""
|
|
274
|
+
装饰器:定义工具函数
|
|
275
|
+
|
|
276
|
+
示例:
|
|
277
|
+
@tool(description="获取指定城市的天气信息")
|
|
278
|
+
def get_weather(city: str, unit: str = "celsius") -> str:
|
|
279
|
+
return f"{city}当前温度25{unit}"
|
|
280
|
+
"""
|
|
281
|
+
def decorator(func: Callable) -> Callable:
|
|
282
|
+
tool_name = name or func.__name__
|
|
283
|
+
tool_desc = description or func.__doc__ or f"执行 {tool_name}"
|
|
284
|
+
|
|
285
|
+
sig = inspect.signature(func)
|
|
286
|
+
hints = get_type_hints(func) if hasattr(func, '__annotations__') else {}
|
|
287
|
+
|
|
288
|
+
parameters = []
|
|
289
|
+
for param_name, param in sig.parameters.items():
|
|
290
|
+
if param_name == 'self':
|
|
291
|
+
continue
|
|
292
|
+
|
|
293
|
+
param_type = ToolRegistry._python_type_to_json(hints.get(param_name, str))
|
|
294
|
+
required = param.default is inspect.Parameter.empty
|
|
295
|
+
default = None if required else param.default
|
|
296
|
+
|
|
297
|
+
parameters.append(ToolParameter(
|
|
298
|
+
name=param_name,
|
|
299
|
+
param_type=param_type,
|
|
300
|
+
description=f"参数 {param_name}",
|
|
301
|
+
required=required,
|
|
302
|
+
default=default
|
|
303
|
+
))
|
|
304
|
+
|
|
305
|
+
tool_def = ToolDefinition(
|
|
306
|
+
name=tool_name,
|
|
307
|
+
description=tool_desc,
|
|
308
|
+
parameters=parameters,
|
|
309
|
+
handler=func
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
func._tool_definition = tool_def # type: ignore[attr-defined]
|
|
313
|
+
|
|
314
|
+
return func
|
|
315
|
+
|
|
316
|
+
return decorator
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def call_with_tools(
|
|
320
|
+
prompt: str,
|
|
321
|
+
tools: List[Union[ToolDefinition, Callable]],
|
|
322
|
+
context: Optional[CognitiveContext] = None,
|
|
323
|
+
*,
|
|
324
|
+
max_iterations: int = 5,
|
|
325
|
+
auto_execute: bool = True,
|
|
326
|
+
**kwargs
|
|
327
|
+
) -> str:
|
|
328
|
+
"""
|
|
329
|
+
执行带工具调用的认知请求
|
|
330
|
+
|
|
331
|
+
参数:
|
|
332
|
+
prompt: 用户提示
|
|
333
|
+
tools: 工具列表(ToolDefinition 或带 @tool 装饰的函数)
|
|
334
|
+
context: 认知上下文
|
|
335
|
+
max_iterations: 最大工具调用迭代次数
|
|
336
|
+
auto_execute: 是否自动执行工具调用
|
|
337
|
+
**kwargs: 其他参数传递给 cognitive_call
|
|
338
|
+
|
|
339
|
+
返回:
|
|
340
|
+
最终响应文本
|
|
341
|
+
|
|
342
|
+
示例:
|
|
343
|
+
@tool
|
|
344
|
+
def search(query: str) -> str:
|
|
345
|
+
return f"搜索结果: {query}"
|
|
346
|
+
|
|
347
|
+
result = call_with_tools(
|
|
348
|
+
"搜索 Python 教程",
|
|
349
|
+
tools=[search]
|
|
350
|
+
)
|
|
351
|
+
"""
|
|
352
|
+
try:
|
|
353
|
+
import openai
|
|
354
|
+
except ImportError:
|
|
355
|
+
raise ImportError("需要安装 openai 包。运行: pip install openai")
|
|
356
|
+
|
|
357
|
+
ctx = context or CognitiveContext.get_current()
|
|
358
|
+
|
|
359
|
+
if ctx is None:
|
|
360
|
+
config = LLMConfig()
|
|
361
|
+
else:
|
|
362
|
+
config = ctx.get_config()
|
|
363
|
+
|
|
364
|
+
if not config.api_key:
|
|
365
|
+
raise ValueError("未配置 API 密钥。")
|
|
366
|
+
|
|
367
|
+
# 构建工具列表
|
|
368
|
+
tool_defs = []
|
|
369
|
+
tool_handlers = {}
|
|
370
|
+
|
|
371
|
+
for t in tools:
|
|
372
|
+
if isinstance(t, ToolDefinition):
|
|
373
|
+
tool_defs.append(t)
|
|
374
|
+
if t.handler:
|
|
375
|
+
tool_handlers[t.name] = t.handler
|
|
376
|
+
elif hasattr(t, '_tool_definition'):
|
|
377
|
+
td = t._tool_definition # type: ignore[attr-defined]
|
|
378
|
+
tool_defs.append(td)
|
|
379
|
+
tool_handlers[td.name] = t
|
|
380
|
+
elif callable(t):
|
|
381
|
+
# 从函数创建工具定义
|
|
382
|
+
td = ToolDefinition(
|
|
383
|
+
name=t.__name__,
|
|
384
|
+
description=t.__doc__ or f"执行 {t.__name__}",
|
|
385
|
+
parameters=[],
|
|
386
|
+
handler=t
|
|
387
|
+
)
|
|
388
|
+
tool_defs.append(td)
|
|
389
|
+
tool_handlers[td.name] = t
|
|
390
|
+
|
|
391
|
+
client = openai.OpenAI(
|
|
392
|
+
api_key=config.api_key,
|
|
393
|
+
base_url=config.base_url
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
messages: List[Dict[str, Any]] = []
|
|
397
|
+
if ctx:
|
|
398
|
+
messages.extend(ctx.get_memory())
|
|
399
|
+
messages.append({"role": "user", "content": prompt})
|
|
400
|
+
|
|
401
|
+
openai_tools = [td.to_openai_tool() for td in tool_defs]
|
|
402
|
+
|
|
403
|
+
iteration = 0
|
|
404
|
+
while iteration < max_iterations:
|
|
405
|
+
response = client.chat.completions.create( # type: ignore[call-overload]
|
|
406
|
+
model=config.model,
|
|
407
|
+
messages=messages,
|
|
408
|
+
tools=openai_tools,
|
|
409
|
+
tool_choice="auto",
|
|
410
|
+
temperature=config.temperature,
|
|
411
|
+
max_tokens=config.max_tokens
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
message = response.choices[0].message
|
|
415
|
+
|
|
416
|
+
# 没有工具调用,返回结果
|
|
417
|
+
if not message.tool_calls:
|
|
418
|
+
result = message.content or ""
|
|
419
|
+
if ctx:
|
|
420
|
+
ctx.add_to_memory("user", prompt)
|
|
421
|
+
ctx.add_to_memory("assistant", result)
|
|
422
|
+
return result
|
|
423
|
+
|
|
424
|
+
# 有工具调用
|
|
425
|
+
messages.append(message)
|
|
426
|
+
|
|
427
|
+
for tool_call in message.tool_calls:
|
|
428
|
+
tc = ToolCall(
|
|
429
|
+
id=tool_call.id,
|
|
430
|
+
name=tool_call.function.name,
|
|
431
|
+
arguments=json.loads(tool_call.function.arguments)
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
if auto_execute and tc.name in tool_handlers:
|
|
435
|
+
try:
|
|
436
|
+
result = tool_handlers[tc.name](**tc.arguments)
|
|
437
|
+
tool_result = ToolResult(
|
|
438
|
+
tool_call_id=tc.id,
|
|
439
|
+
name=tc.name,
|
|
440
|
+
arguments=tc.arguments,
|
|
441
|
+
result=result
|
|
442
|
+
)
|
|
443
|
+
except Exception as e:
|
|
444
|
+
tool_result = ToolResult(
|
|
445
|
+
tool_call_id=tc.id,
|
|
446
|
+
name=tc.name,
|
|
447
|
+
arguments=tc.arguments,
|
|
448
|
+
result=None,
|
|
449
|
+
error=str(e)
|
|
450
|
+
)
|
|
451
|
+
|
|
452
|
+
messages.append(tool_result.to_openai_format())
|
|
453
|
+
|
|
454
|
+
iteration += 1
|
|
455
|
+
|
|
456
|
+
# 达到最大迭代次数,返回最后响应
|
|
457
|
+
final_response = client.chat.completions.create(
|
|
458
|
+
model=config.model,
|
|
459
|
+
messages=messages, # type: ignore[arg-type]
|
|
460
|
+
temperature=config.temperature,
|
|
461
|
+
max_tokens=config.max_tokens
|
|
462
|
+
)
|
|
463
|
+
|
|
464
|
+
return final_response.choices[0].message.content or ""
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
# 全局工具注册表
|
|
468
|
+
_global_registry = ToolRegistry()
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
def register_tool(
|
|
472
|
+
name: Optional[str] = None,
|
|
473
|
+
description: str = ""
|
|
474
|
+
) -> Callable:
|
|
475
|
+
"""注册工具到全局注册表"""
|
|
476
|
+
return _global_registry.register(name=name, description=description)
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
def get_global_registry() -> ToolRegistry:
|
|
480
|
+
"""获取全局工具注册表"""
|
|
481
|
+
return _global_registry
|
codegnipy/transformer.py
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AST 转换器模块
|
|
3
|
+
|
|
4
|
+
将 `~"prompt"` 语法转换为 `cognitive_call("prompt")` 调用。
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import ast
|
|
8
|
+
import types
|
|
9
|
+
from typing import Optional
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class CognitiveTransformer(ast.NodeTransformer):
|
|
13
|
+
"""
|
|
14
|
+
AST 转换器:将认知操作符转换为运行时调用。
|
|
15
|
+
|
|
16
|
+
转换规则:
|
|
17
|
+
~"prompt" → cognitive_call("prompt")
|
|
18
|
+
~variable → cognitive_call(variable)
|
|
19
|
+
~(expr) → cognitive_call(expr)
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(self, context_name: str = "__cognitive_context__"):
|
|
23
|
+
super().__init__()
|
|
24
|
+
self.context_name = context_name
|
|
25
|
+
|
|
26
|
+
def _transform_invert(self, node: ast.UnaryOp) -> ast.Call:
|
|
27
|
+
"""
|
|
28
|
+
处理 `~` 操作符(一元取反操作符被借用为认知操作符)
|
|
29
|
+
|
|
30
|
+
将 ~expr 转换为 cognitive_call(expr)
|
|
31
|
+
"""
|
|
32
|
+
# 递归处理嵌套的节点
|
|
33
|
+
operand = self.visit(node.operand)
|
|
34
|
+
|
|
35
|
+
# 构建 cognitive_call(...) 调用
|
|
36
|
+
call = ast.Call(
|
|
37
|
+
func=ast.Attribute(
|
|
38
|
+
value=ast.Name(id="codegnipy", ctx=ast.Load()),
|
|
39
|
+
attr="cognitive_call",
|
|
40
|
+
ctx=ast.Load()
|
|
41
|
+
),
|
|
42
|
+
args=[operand],
|
|
43
|
+
keywords=[
|
|
44
|
+
ast.keyword(
|
|
45
|
+
arg="context",
|
|
46
|
+
value=ast.Name(id=self.context_name, ctx=ast.Load())
|
|
47
|
+
)
|
|
48
|
+
]
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
# 设置行号信息用于调试
|
|
52
|
+
ast.copy_location(call, node)
|
|
53
|
+
ast.fix_missing_locations(call)
|
|
54
|
+
|
|
55
|
+
return call
|
|
56
|
+
|
|
57
|
+
def visit_UnaryOp(self, node: ast.UnaryOp) -> ast.AST:
|
|
58
|
+
"""
|
|
59
|
+
处理一元操作
|
|
60
|
+
|
|
61
|
+
只有 `~` 操作符需要特殊处理,其他保持原样。
|
|
62
|
+
"""
|
|
63
|
+
if isinstance(node.op, ast.Invert):
|
|
64
|
+
return self._transform_invert(node)
|
|
65
|
+
|
|
66
|
+
# 其他一元操作符保持原样
|
|
67
|
+
return self.generic_visit(node)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def transform_code(source: str, filename: str = "<codegnipy>") -> ast.Module:
|
|
71
|
+
"""
|
|
72
|
+
转换 Codegnipy 源代码,返回转换后的 AST。
|
|
73
|
+
|
|
74
|
+
参数:
|
|
75
|
+
source: Python 源代码字符串
|
|
76
|
+
filename: 文件名(用于错误信息)
|
|
77
|
+
|
|
78
|
+
返回:
|
|
79
|
+
转换后的 AST 模块
|
|
80
|
+
|
|
81
|
+
示例:
|
|
82
|
+
source = 'result = ~"你好"'
|
|
83
|
+
tree = transform_code(source)
|
|
84
|
+
# tree 现在包含: result = codegnipy.cognitive_call("你好", context=__cognitive_context__)
|
|
85
|
+
"""
|
|
86
|
+
# 解析源代码
|
|
87
|
+
tree = ast.parse(source, filename=filename)
|
|
88
|
+
|
|
89
|
+
# 应用转换
|
|
90
|
+
transformer = CognitiveTransformer()
|
|
91
|
+
new_tree = transformer.visit(tree)
|
|
92
|
+
|
|
93
|
+
# 修复位置信息
|
|
94
|
+
ast.fix_missing_locations(new_tree)
|
|
95
|
+
|
|
96
|
+
return new_tree
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def compile_codegnipy(
|
|
100
|
+
source: str,
|
|
101
|
+
filename: str = "<codegnipy>",
|
|
102
|
+
mode: str = "exec"
|
|
103
|
+
) -> types.CodeType:
|
|
104
|
+
"""
|
|
105
|
+
编译 Codegnipy 源代码,返回代码对象。
|
|
106
|
+
|
|
107
|
+
参数:
|
|
108
|
+
source: Python 源代码字符串
|
|
109
|
+
filename: 文件名
|
|
110
|
+
mode: 编译模式 ('exec', 'eval', 'single')
|
|
111
|
+
|
|
112
|
+
返回:
|
|
113
|
+
编译后的代码对象
|
|
114
|
+
"""
|
|
115
|
+
tree = transform_code(source, filename)
|
|
116
|
+
return compile(tree, filename, mode)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def exec_codegnipy(
|
|
120
|
+
source: str,
|
|
121
|
+
globals_: Optional[dict] = None,
|
|
122
|
+
locals_: Optional[dict] = None,
|
|
123
|
+
filename: str = "<codegnipy>"
|
|
124
|
+
) -> dict:
|
|
125
|
+
"""
|
|
126
|
+
执行 Codegnipy 源代码。
|
|
127
|
+
|
|
128
|
+
参数:
|
|
129
|
+
source: Python 源代码字符串
|
|
130
|
+
globals_: 全局命名空间
|
|
131
|
+
locals_: 局部命名空间
|
|
132
|
+
filename: 文件名
|
|
133
|
+
|
|
134
|
+
返回:
|
|
135
|
+
执行后的局部命名空间
|
|
136
|
+
"""
|
|
137
|
+
from .runtime import CognitiveContext
|
|
138
|
+
|
|
139
|
+
if globals_ is None:
|
|
140
|
+
globals_ = {}
|
|
141
|
+
if locals_ is None:
|
|
142
|
+
locals_ = {}
|
|
143
|
+
|
|
144
|
+
# 确保 codegnipy 模块可用
|
|
145
|
+
import codegnipy
|
|
146
|
+
globals_['codegnipy'] = codegnipy
|
|
147
|
+
|
|
148
|
+
# 创建上下文变量
|
|
149
|
+
globals_['__cognitive_context__'] = CognitiveContext.get_current()
|
|
150
|
+
|
|
151
|
+
# 编译并执行
|
|
152
|
+
code = compile_codegnipy(source, filename)
|
|
153
|
+
exec(code, globals_, locals_)
|
|
154
|
+
|
|
155
|
+
return locals_
|