gomyck-tools 1.0.0__py3-none-any.whl → 1.4.7__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.
- ctools/__init__.py +21 -0
- ctools/ai/__init__.py +4 -0
- ctools/ai/llm_chat.py +184 -0
- ctools/ai/llm_client.py +163 -0
- ctools/ai/llm_exception.py +17 -0
- ctools/ai/mcp/__init__.py +4 -0
- ctools/ai/mcp/mcp_client.py +326 -0
- ctools/ai/tools/__init__.py +4 -0
- ctools/ai/tools/json_extract.py +202 -0
- ctools/ai/tools/quick_tools.py +149 -0
- ctools/ai/tools/think_process.py +11 -0
- ctools/ai/tools/tool_use_xml_parse.py +40 -0
- ctools/ai/tools/xml_extract.py +15 -0
- ctools/application.py +50 -47
- ctools/aspect.py +65 -0
- ctools/auto/__init__.py +4 -0
- ctools/{browser_element_tools.py → auto/browser_element.py} +18 -8
- ctools/{plan_area_tools.py → auto/plan_area.py} +5 -7
- ctools/{pty_tools.py → auto/pty_process.py} +6 -3
- ctools/{resource_bundle_tools.py → auto/resource_bundle.py} +4 -4
- ctools/{screenshot_tools.py → auto/screenshot.py} +7 -6
- ctools/{win_canvas.py → auto/win_canvas.py} +10 -4
- ctools/{win_control.py → auto/win_control.py} +14 -5
- ctools/call.py +34 -49
- ctools/cdate.py +84 -0
- ctools/cdebug.py +146 -0
- ctools/cid.py +20 -0
- ctools/cipher/__init__.py +4 -0
- ctools/{aes_tools.py → cipher/aes_util.py} +10 -0
- ctools/{b64.py → cipher/b64.py} +2 -0
- ctools/cipher/czip.py +133 -0
- ctools/cipher/rsa.py +75 -0
- ctools/{sign.py → cipher/sign.py} +2 -1
- ctools/{sm_tools.py → cipher/sm_util.py} +20 -4
- ctools/cjson.py +10 -10
- ctools/cron_lite.py +109 -97
- ctools/database/__init__.py +4 -0
- ctools/{database.py → database/database.py} +93 -26
- ctools/dict_wrapper.py +21 -0
- ctools/ex.py +4 -0
- ctools/geo/__init__.py +4 -0
- ctools/geo/coord_trans.py +127 -0
- ctools/geo/douglas_rarefy.py +139 -0
- ctools/metrics.py +56 -63
- ctools/office/__init__.py +4 -0
- ctools/office/cword.py +30 -0
- ctools/{word_fill.py → office/word_fill.py} +3 -6
- ctools/patch.py +88 -0
- ctools/{work_path.py → path_info.py} +34 -2
- ctools/pkg/__init__.py +4 -0
- ctools/pkg/dynamic_imp.py +38 -0
- ctools/pools/__init__.py +4 -0
- ctools/pools/process_pool.py +41 -0
- ctools/{thread_pool.py → pools/thread_pool.py} +13 -4
- ctools/similar.py +25 -0
- ctools/stream/__init__.py +4 -0
- ctools/stream/ckafka.py +164 -0
- ctools/stream/credis.py +127 -0
- ctools/{mqtt_utils.py → stream/mqtt_utils.py} +20 -12
- ctools/sys_info.py +36 -13
- ctools/sys_log.py +46 -27
- ctools/util/__init__.py +4 -0
- ctools/util/cftp.py +76 -0
- ctools/util/cklock.py +118 -0
- ctools/util/config_util.py +52 -0
- ctools/util/env_config.py +63 -0
- ctools/{html_soup.py → util/html_soup.py} +0 -7
- ctools/{http_utils.py → util/http_util.py} +4 -2
- ctools/{images_tools.py → util/image_process.py} +10 -1
- ctools/util/jb_cut.py +54 -0
- ctools/{id_worker_tools.py → util/snow_id.py} +8 -23
- ctools/web/__init__.py +4 -0
- ctools/web/aio_web_server.py +186 -0
- ctools/web/api_result.py +56 -0
- ctools/web/bottle_web_base.py +239 -0
- ctools/web/bottle_webserver.py +191 -0
- ctools/web/bottle_websocket.py +79 -0
- ctools/web/ctoken.py +103 -0
- ctools/{download_tools.py → web/download_util.py} +14 -12
- ctools/web/params_util.py +46 -0
- ctools/{upload_tools.py → web/upload_util.py} +3 -2
- gomyck_tools-1.4.7.dist-info/METADATA +70 -0
- gomyck_tools-1.4.7.dist-info/RECORD +88 -0
- {gomyck_tools-1.0.0.dist-info → gomyck_tools-1.4.7.dist-info}/WHEEL +1 -1
- gomyck_tools-1.4.7.dist-info/licenses/LICENSE +13 -0
- ctools/bashPath.py +0 -13
- ctools/bottle_server.py +0 -49
- ctools/console.py +0 -55
- ctools/date_utils.py +0 -44
- ctools/enums.py +0 -4
- ctools/excelOpt.py +0 -36
- ctools/imgDialog.py +0 -44
- ctools/license.py +0 -37
- ctools/log.py +0 -28
- ctools/mvc.py +0 -56
- ctools/obj.py +0 -20
- ctools/pacth.py +0 -73
- ctools/ssh.py +0 -9
- ctools/strDiff.py +0 -20
- ctools/string_tools.py +0 -101
- ctools/token_tools.py +0 -13
- ctools/wordFill.py +0 -24
- gomyck_tools-1.0.0.dist-info/METADATA +0 -20
- gomyck_tools-1.0.0.dist-info/RECORD +0 -54
- /ctools/{word_fill_entity.py → office/word_fill_entity.py} +0 -0
- /ctools/{compile_tools.py → util/compile_util.py} +0 -0
- {gomyck_tools-1.0.0.dist-info → gomyck_tools-1.4.7.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: UTF-8 -*-
|
|
3
|
+
__author__ = 'haoyang'
|
|
4
|
+
__date__ = '2025/5/16 16:49'
|
|
5
|
+
|
|
6
|
+
import asyncio
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
import shutil
|
|
10
|
+
from contextlib import AsyncExitStack, asynccontextmanager
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from mcp import ClientSession, StdioServerParameters
|
|
14
|
+
from mcp.client.sse import sse_client
|
|
15
|
+
from mcp.client.stdio import stdio_client
|
|
16
|
+
from mcp.client.streamable_http import streamablehttp_client
|
|
17
|
+
from mcp.types import CallToolResult, TextContent, ImageContent
|
|
18
|
+
|
|
19
|
+
from ctools import sys_log, cdate
|
|
20
|
+
from ctools.ai.tools.tool_use_xml_parse import parse_tool_use
|
|
21
|
+
|
|
22
|
+
log = sys_log.flog
|
|
23
|
+
|
|
24
|
+
sys_prompt_4_mcp = """
|
|
25
|
+
1. In this environment you have access to a set of tools you can use to answer the user's question.
|
|
26
|
+
2. You can use one tool per message, and will receive the result of that tool use in the user's response.
|
|
27
|
+
3. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use.
|
|
28
|
+
4. Before solving the task, break it down into clear, logical steps. List and number the steps first. Then, execute them one by one, There is no need to explain each step as you go. Do not skip any steps. Wait for confirmation before proceeding to the next step, if needed.
|
|
29
|
+
|
|
30
|
+
## Tool Use Formatting
|
|
31
|
+
|
|
32
|
+
Tool use is formatted using XML-style tags. The tool name is enclosed in opening and closing tags, and each parameter is similarly enclosed within its own set of tags. Here's the structure:
|
|
33
|
+
<tool_use>
|
|
34
|
+
<name>{{tool_name}}</name>
|
|
35
|
+
<arguments>{{json_arguments}}</arguments>
|
|
36
|
+
</tool_use>
|
|
37
|
+
|
|
38
|
+
The tool name should be the exact name of the tool you are using, and the arguments should be a JSON object containing the parameters required by that tool. For example:
|
|
39
|
+
|
|
40
|
+
<tool_use>
|
|
41
|
+
<name>say_hello</name>
|
|
42
|
+
<arguments>
|
|
43
|
+
{{
|
|
44
|
+
"content": "你好"
|
|
45
|
+
}}
|
|
46
|
+
</arguments>
|
|
47
|
+
</tool_use>
|
|
48
|
+
|
|
49
|
+
The result should be a string, which can represent a file or any other output type. You can use this result as input for the next action.
|
|
50
|
+
|
|
51
|
+
Always adhere to this format for the tool use to ensure proper parsing and execution.
|
|
52
|
+
|
|
53
|
+
## Tool Use Available Tools
|
|
54
|
+
Above example were using notional tools that might not exist for you. You only have access to these tools:
|
|
55
|
+
{tools_description}
|
|
56
|
+
|
|
57
|
+
## Tool Use Rules
|
|
58
|
+
Here are the rules you should always follow to solve your task:
|
|
59
|
+
1. Always use the right arguments for the tools. Never use variable names as the action arguments, use the value instead.
|
|
60
|
+
2. Call a tool only when needed: do not call the search agent if you do not need information, try to solve the task yourself.
|
|
61
|
+
3. If no tool call is needed, just answer the question directly.
|
|
62
|
+
4. Never re-do a tool call that you previously did with the exact same parameters.
|
|
63
|
+
5. For tool use, MARK SURE use XML tag format as shown in the examples above. Do not use any other format.
|
|
64
|
+
6. Parameter passing should never escape unicode, and this is done by default, do not convert Chinese to Unicode escape characters
|
|
65
|
+
|
|
66
|
+
# User Instructions
|
|
67
|
+
{user_system_prompt}
|
|
68
|
+
|
|
69
|
+
# Current time
|
|
70
|
+
{current_time}
|
|
71
|
+
|
|
72
|
+
Now Begin! If you solve the task correctly, you will receive a reward of $1,000,000.
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
tools_use_example = """
|
|
76
|
+
Here are a few examples using notional tools:
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
User: "What is the result of the following operation: 5 + 3 + 1294.678?"
|
|
80
|
+
|
|
81
|
+
Assistant: I can use the python_interpreter tool to calculate the result of the operation.
|
|
82
|
+
<tool_use>
|
|
83
|
+
<name>python_interpreter</name>
|
|
84
|
+
<arguments>{"code": "5 + 3 + 1294.678"}</arguments>
|
|
85
|
+
</tool_use>
|
|
86
|
+
|
|
87
|
+
User: {
|
|
88
|
+
"tool_name": "python_interpreter",
|
|
89
|
+
"result": ["1302.678"]
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
Assistant: The result of the operation is 1302.678.
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
User: "Which city has the highest population , Guangzhou or Shanghai?"
|
|
96
|
+
|
|
97
|
+
Assistant: I can use the search tool to find the population of Guangzhou.
|
|
98
|
+
<tool_use>
|
|
99
|
+
<name>search</name>
|
|
100
|
+
<arguments>{"query": "Population Guangzhou"}</arguments>
|
|
101
|
+
</tool_use>
|
|
102
|
+
|
|
103
|
+
User: {
|
|
104
|
+
"tool_name": "search",
|
|
105
|
+
"result": ["Guangzhou has a population of 15 million inhabitants as of 2021."]
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
Assistant: I can use the search tool to find the population of Shanghai.
|
|
109
|
+
<tool_use>
|
|
110
|
+
<name>search</name>
|
|
111
|
+
<arguments>{"query": "Population Shanghai"}</arguments>
|
|
112
|
+
</tool_use>
|
|
113
|
+
|
|
114
|
+
User: {
|
|
115
|
+
"tool_name": "search",
|
|
116
|
+
"result": ["26 million (2019)"]
|
|
117
|
+
}
|
|
118
|
+
Assistant: The population of Shanghai is 26 million, while Guangzhou has a population of 15 million. Therefore, Shanghai has the highest population.
|
|
119
|
+
"""
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
async def get_tools_prompt(mcp_clients, user_system_prompt) -> str:
|
|
123
|
+
all_tools = []
|
|
124
|
+
for client in mcp_clients:
|
|
125
|
+
tools = await client.list_server_tools()
|
|
126
|
+
all_tools.extend(tools)
|
|
127
|
+
return sys_prompt_4_mcp.format(tools_description="\n".join([tool.format_for_llm() for tool in all_tools]), user_system_prompt=user_system_prompt, current_time=cdate.get_date())
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class Tool:
|
|
131
|
+
|
|
132
|
+
def __init__(self, name: str, description: str, input_schema: dict[str, Any]) -> None:
|
|
133
|
+
self.name: str = name
|
|
134
|
+
self.description: str = description
|
|
135
|
+
self.input_schema: dict[str, Any] = input_schema
|
|
136
|
+
|
|
137
|
+
def format_for_llm(self) -> str:
|
|
138
|
+
args_desc = []
|
|
139
|
+
if "properties" in self.input_schema:
|
|
140
|
+
for param_name, param_info in self.input_schema["properties"].items():
|
|
141
|
+
arg_desc = f"- {param_name}({param_info.get('type', 'Any')}): {param_info.get('description', '')}"
|
|
142
|
+
if param_name in self.input_schema.get("required", []):
|
|
143
|
+
arg_desc += " (required)"
|
|
144
|
+
args_desc.append(arg_desc)
|
|
145
|
+
return f"""
|
|
146
|
+
Tool: {self.name}
|
|
147
|
+
Description: {self.description}
|
|
148
|
+
Args_Info:
|
|
149
|
+
{chr(10).join(args_desc)}
|
|
150
|
+
"""
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
class MCPClient:
|
|
154
|
+
|
|
155
|
+
def __init__(self, name: str, config: dict[str, Any]) -> None:
|
|
156
|
+
self.name: str = name
|
|
157
|
+
self.config: dict[str, Any] = config
|
|
158
|
+
self.stdio_context: Any | None = None
|
|
159
|
+
self.session: ClientSession | None = None
|
|
160
|
+
self.exit_stack: AsyncExitStack = AsyncExitStack()
|
|
161
|
+
self.tools = []
|
|
162
|
+
|
|
163
|
+
async def initialize(self) -> None:
|
|
164
|
+
if self.config.get('server_type') is None or self.config.get('server_type') == 'stdio':
|
|
165
|
+
command = (shutil.which("npx") if self.config["command"] == "npx" else self.config["command"])
|
|
166
|
+
if command is None: raise ValueError("The command must be a valid string and cannot be None.")
|
|
167
|
+
env = os.environ.copy()
|
|
168
|
+
custom_env = self.config.get("env", {})
|
|
169
|
+
env.update(custom_env)
|
|
170
|
+
server_params = StdioServerParameters(
|
|
171
|
+
command=command,
|
|
172
|
+
args=self.config["args"],
|
|
173
|
+
env=env,
|
|
174
|
+
)
|
|
175
|
+
stdio_transport = await self.exit_stack.enter_async_context(stdio_client(server_params))
|
|
176
|
+
read, write = stdio_transport
|
|
177
|
+
self.session = await self.exit_stack.enter_async_context(ClientSession(read, write))
|
|
178
|
+
await self.session.initialize()
|
|
179
|
+
elif self.config['server_type'] == 'SSE':
|
|
180
|
+
sse_transport = await self.exit_stack.enter_async_context(sse_client(
|
|
181
|
+
url=self.config["url"],
|
|
182
|
+
headers=self.config["headers"],
|
|
183
|
+
timeout=self.config["timeout"],
|
|
184
|
+
sse_read_timeout=self.config["sse_read_timeout"]))
|
|
185
|
+
read, write = sse_transport
|
|
186
|
+
self.session = await self.exit_stack.enter_async_context(ClientSession(read, write))
|
|
187
|
+
await self.session.initialize()
|
|
188
|
+
elif self.config['server_type'] == 'Streamable HTTP':
|
|
189
|
+
stream_transport = await self.exit_stack.enter_async_context(streamablehttp_client(
|
|
190
|
+
url=self.config["url"],
|
|
191
|
+
headers=self.config["headers"],
|
|
192
|
+
timeout=self.config["timeout"],
|
|
193
|
+
sse_read_timeout=self.config["sse_read_timeout"]))
|
|
194
|
+
read, write, session_id = stream_transport
|
|
195
|
+
self.session = await self.exit_stack.enter_async_context(ClientSession(read, write))
|
|
196
|
+
await self.session.initialize()
|
|
197
|
+
|
|
198
|
+
async def list_server_tools(self) -> list[Any]:
|
|
199
|
+
if not self.session: raise RuntimeError(f"Server {self.name} not initialized")
|
|
200
|
+
if self.tools: return self.tools
|
|
201
|
+
tools_response = await self.session.list_tools()
|
|
202
|
+
for item in tools_response:
|
|
203
|
+
if isinstance(item, tuple) and item[0] == "tools":
|
|
204
|
+
self.tools.extend(Tool(tool.name, tool.description, tool.inputSchema) for tool in item[1])
|
|
205
|
+
return self.tools
|
|
206
|
+
|
|
207
|
+
async def execute_tool(
|
|
208
|
+
self,
|
|
209
|
+
tool_name: str,
|
|
210
|
+
arguments: dict[str, Any],
|
|
211
|
+
retries: int = 3,
|
|
212
|
+
delay: float = 1.0,
|
|
213
|
+
) -> Any:
|
|
214
|
+
if not self.session: raise RuntimeError(f"Server {self.name} not initialized")
|
|
215
|
+
attempt = 0
|
|
216
|
+
args = arguments
|
|
217
|
+
while attempt < retries:
|
|
218
|
+
try:
|
|
219
|
+
log.info(f"Executing {tool_name}...")
|
|
220
|
+
result = await self.session.call_tool(tool_name, args)
|
|
221
|
+
return result
|
|
222
|
+
except Exception as e:
|
|
223
|
+
attempt += 1
|
|
224
|
+
log.warning(f"Error executing tool: {e}. Attempt {attempt} of {retries}.")
|
|
225
|
+
if attempt < retries:
|
|
226
|
+
log.info(f"Retrying in {delay} seconds...")
|
|
227
|
+
await asyncio.sleep(delay)
|
|
228
|
+
else:
|
|
229
|
+
log.error("Max retries reached. Failing.")
|
|
230
|
+
raise
|
|
231
|
+
|
|
232
|
+
async def cleanup(self) -> None:
|
|
233
|
+
await self.exit_stack.aclose()
|
|
234
|
+
self.session = None
|
|
235
|
+
self.stdio_context = None
|
|
236
|
+
self.exit_stack = None
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
async def mcp_tool_call(mcp_clients: MCPClient, xml_info: str) -> str:
|
|
240
|
+
if not mcp_clients: return xml_info
|
|
241
|
+
final_result = {
|
|
242
|
+
"tool_name": "",
|
|
243
|
+
"result": []
|
|
244
|
+
}
|
|
245
|
+
try:
|
|
246
|
+
tool_call = parse_tool_use(xml_info)
|
|
247
|
+
if "tool" in tool_call and "arguments" in tool_call:
|
|
248
|
+
log.info(f"Executing tool: {tool_call['tool']} With arguments: {tool_call['arguments']}")
|
|
249
|
+
for client in mcp_clients:
|
|
250
|
+
tools = await client.list_server_tools()
|
|
251
|
+
if any(tool.name == tool_call["tool"] for tool in tools):
|
|
252
|
+
final_result["tool_name"] = tool_call["tool"]
|
|
253
|
+
try:
|
|
254
|
+
result: CallToolResult = await client.execute_tool(tool_call["tool"], tool_call["arguments"])
|
|
255
|
+
text_result = []
|
|
256
|
+
image_result = []
|
|
257
|
+
tools_call_content = result.content
|
|
258
|
+
for content in tools_call_content:
|
|
259
|
+
if type(content) == TextContent:
|
|
260
|
+
try:
|
|
261
|
+
text_result.append(json.loads(content.text))
|
|
262
|
+
except Exception as e:
|
|
263
|
+
text_result.append(content.text)
|
|
264
|
+
elif type(content) == ImageContent:
|
|
265
|
+
image_result.append({"mime_type": content.mimeType, "data": content.data})
|
|
266
|
+
text_result.extend(image_result)
|
|
267
|
+
final_result["result"] = text_result
|
|
268
|
+
return final_result
|
|
269
|
+
except Exception as e:
|
|
270
|
+
log.exception(e)
|
|
271
|
+
error_msg = f"Error executing tool: {str(e)}"
|
|
272
|
+
final_result["result"] = [error_msg]
|
|
273
|
+
return final_result
|
|
274
|
+
return f"No server found with tool: {tool_call['tool']}"
|
|
275
|
+
return xml_info
|
|
276
|
+
except Exception as e:
|
|
277
|
+
log.exception(e)
|
|
278
|
+
error_msg = f"Error executing tool: {str(e)}"
|
|
279
|
+
final_result["result"] = [error_msg]
|
|
280
|
+
return final_result
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def res_has_img(llm_response) -> bool:
|
|
284
|
+
if type(llm_response) == str: return False
|
|
285
|
+
response: [] = llm_response.get("result")
|
|
286
|
+
for rep in response:
|
|
287
|
+
if is_image_content(rep):
|
|
288
|
+
return True
|
|
289
|
+
return False
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def is_image_content(content: dict) -> bool:
|
|
293
|
+
try:
|
|
294
|
+
if content.get("mime_type") and content.get("data"):
|
|
295
|
+
return True
|
|
296
|
+
return False
|
|
297
|
+
except Exception:
|
|
298
|
+
return False
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
from contextlib import asynccontextmanager
|
|
302
|
+
|
|
303
|
+
@asynccontextmanager
|
|
304
|
+
async def init_mcp_clients(mcp_server_config: dict[str, Any]) -> list[MCPClient]:
|
|
305
|
+
mcp_clients = []
|
|
306
|
+
if not mcp_server_config["mcpServers"]:
|
|
307
|
+
yield mcp_clients
|
|
308
|
+
return # 这句必须有,避免进入 finally
|
|
309
|
+
try:
|
|
310
|
+
for name, sc in mcp_server_config["mcpServers"].items():
|
|
311
|
+
try:
|
|
312
|
+
mc = MCPClient(name, sc)
|
|
313
|
+
await mc.initialize()
|
|
314
|
+
mcp_clients.append(mc)
|
|
315
|
+
except Exception as e:
|
|
316
|
+
log.exception(f"Error initializing MCP server [{name}]: {e}")
|
|
317
|
+
yield mcp_clients # 只允许有一个 yield
|
|
318
|
+
finally:
|
|
319
|
+
for client in mcp_clients:
|
|
320
|
+
try:
|
|
321
|
+
print(client.name)
|
|
322
|
+
await client.cleanup()
|
|
323
|
+
except* Exception as eg:
|
|
324
|
+
#log.warning(f"ExceptionGroup unloading MCP server [{client.name}]: {eg}")
|
|
325
|
+
pass
|
|
326
|
+
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: UTF-8 -*-
|
|
3
|
+
__author__ = 'haoyang'
|
|
4
|
+
__date__ = '2025/5/19 16:45'
|
|
5
|
+
|
|
6
|
+
import re
|
|
7
|
+
|
|
8
|
+
from ctools import cjson
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def extract_json_from_text(text):
|
|
12
|
+
"""
|
|
13
|
+
从混杂文本中提取第一个完整的 JSON 对象
|
|
14
|
+
"""
|
|
15
|
+
import json
|
|
16
|
+
|
|
17
|
+
# 方法1:尝试直接解析
|
|
18
|
+
try:
|
|
19
|
+
return json.loads(text)
|
|
20
|
+
except json.JSONDecodeError:
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
# 方法2:字符级括号匹配提取 JSON
|
|
24
|
+
start = None
|
|
25
|
+
brace_count = 0
|
|
26
|
+
for i, char in enumerate(text):
|
|
27
|
+
if char == '{':
|
|
28
|
+
if start is None:
|
|
29
|
+
start = i
|
|
30
|
+
brace_count += 1
|
|
31
|
+
elif char == '}':
|
|
32
|
+
brace_count -= 1
|
|
33
|
+
if brace_count == 0 and start is not None:
|
|
34
|
+
json_candidate = text[start:i + 1]
|
|
35
|
+
try:
|
|
36
|
+
return json.loads(json_candidate)
|
|
37
|
+
except json.JSONDecodeError:
|
|
38
|
+
start = None # 重置继续寻找下一个可能的 JSON
|
|
39
|
+
|
|
40
|
+
# 方法3:尝试 JSONP 格式
|
|
41
|
+
match = re.search(r'\((\{[\s\S]*\})\)', text)
|
|
42
|
+
if match:
|
|
43
|
+
try:
|
|
44
|
+
return json.loads(match.group(1))
|
|
45
|
+
except json.JSONDecodeError:
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
return None
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
if __name__ == '__main__':
|
|
52
|
+
xx = """
|
|
53
|
+
<think>
|
|
54
|
+
|
|
55
|
+
</think>
|
|
56
|
+
|
|
57
|
+
{
|
|
58
|
+
"sjbs": {
|
|
59
|
+
"xsbt": "东都公园业主集体信访事件",
|
|
60
|
+
"sjbh": "202406031234",
|
|
61
|
+
"jjcd": ["黄"]
|
|
62
|
+
},
|
|
63
|
+
"skys": {
|
|
64
|
+
"zxsj": [
|
|
65
|
+
{
|
|
66
|
+
"jqsj": "2024-06-03 09:00",
|
|
67
|
+
"sjms": "6月3日",
|
|
68
|
+
"sjlx": ["计划时间"]
|
|
69
|
+
}
|
|
70
|
+
],
|
|
71
|
+
"zxdd": [
|
|
72
|
+
{
|
|
73
|
+
"bzdz": "黑龙江省哈尔滨市信访局",
|
|
74
|
+
"cslx": ["政府机关"]
|
|
75
|
+
}
|
|
76
|
+
]
|
|
77
|
+
},
|
|
78
|
+
"ssqt": {
|
|
79
|
+
"qtms": ["哈尔滨市道外区东都公园业主"],
|
|
80
|
+
"qtgm": ["约5人以上,可能发展至群体性事件"],
|
|
81
|
+
"qtbq": ["房地产纠纷", "历史遗留问题"],
|
|
82
|
+
"zztz": ["有核心组织"]
|
|
83
|
+
},
|
|
84
|
+
"ryqd": [
|
|
85
|
+
{
|
|
86
|
+
"xm": ["杨开亮"],
|
|
87
|
+
"sfzh": ["2301251968101335**"],
|
|
88
|
+
"js": ["组织者"],
|
|
89
|
+
"hjd": ["哈尔滨市宾县满井镇永宁村崔海屯"],
|
|
90
|
+
"jzd": ["团结镇东都公元一区五栋二单元603"],
|
|
91
|
+
"lxdh": ["139366789**"],
|
|
92
|
+
"rybq": ["重点人"],
|
|
93
|
+
"wlzh": {
|
|
94
|
+
"wx": [],
|
|
95
|
+
"qq": []
|
|
96
|
+
},
|
|
97
|
+
"gjxx": [
|
|
98
|
+
{
|
|
99
|
+
"sj": ["2024-05-28 20:26"],
|
|
100
|
+
"dd": ["网络群聊"],
|
|
101
|
+
"xw": ["组织动员"]
|
|
102
|
+
}
|
|
103
|
+
]
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
"xm": ["孙凤玲"],
|
|
107
|
+
"sfzh": ["2301041955121712**"],
|
|
108
|
+
"js": ["积极参与者"],
|
|
109
|
+
"hjd": ["哈尔滨市道外区迎新街好民居滨港水岸D15栋1单元14层4号"],
|
|
110
|
+
"jzd": ["道外区陶瓷小区D15-1-1404"],
|
|
111
|
+
"lxdh": ["17758887348"],
|
|
112
|
+
"rybq": [],
|
|
113
|
+
"wlzh": {
|
|
114
|
+
"wx": [],
|
|
115
|
+
"qq": []
|
|
116
|
+
},
|
|
117
|
+
"gjxx": [
|
|
118
|
+
{
|
|
119
|
+
"sj": ["2024-05-28 19:00"],
|
|
120
|
+
"dd": ["网络群聊"],
|
|
121
|
+
"xw": ["响应组织"]
|
|
122
|
+
}
|
|
123
|
+
]
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
"xm": ["高秀艳"],
|
|
127
|
+
"sfzh": ["2323261982060762**"],
|
|
128
|
+
"js": ["积极参与者"],
|
|
129
|
+
"hjd": ["绥化市青冈县劳动乡北斗村丛喜云屯"],
|
|
130
|
+
"jzd": ["哈尔滨市道外区团结镇森桐木业"],
|
|
131
|
+
"lxdh": ["15846349146"],
|
|
132
|
+
"rybq": [],
|
|
133
|
+
"wlzh": {
|
|
134
|
+
"wx": [],
|
|
135
|
+
"qq": []
|
|
136
|
+
},
|
|
137
|
+
"gjxx": [
|
|
138
|
+
{
|
|
139
|
+
"sj": ["2024-05-28 20:00"],
|
|
140
|
+
"dd": ["网络群聊"],
|
|
141
|
+
"xw": ["响应组织"]
|
|
142
|
+
}
|
|
143
|
+
]
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
"xm": ["高振凤"],
|
|
147
|
+
"sfzh": ["2323031974103046**"],
|
|
148
|
+
"js": ["一般参与者"],
|
|
149
|
+
"hjd": ["绥化市肇东市东发乡夕阳村郭家屯"],
|
|
150
|
+
"jzd": ["团结镇团结镇东都公园一区七栋一单元101"],
|
|
151
|
+
"lxdh": ["18004659805"],
|
|
152
|
+
"rybq": [],
|
|
153
|
+
"wlzh": {
|
|
154
|
+
"wx": [],
|
|
155
|
+
"qq": []
|
|
156
|
+
},
|
|
157
|
+
"gjxx": [
|
|
158
|
+
{
|
|
159
|
+
"sj": ["2024-05-28 19:30"],
|
|
160
|
+
"dd": ["网络群聊"],
|
|
161
|
+
"xw": ["响应组织"]
|
|
162
|
+
}
|
|
163
|
+
]
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
"xm": ["陈立军"],
|
|
167
|
+
"sfzh": ["2301251980031907**"],
|
|
168
|
+
"js": ["组织者", "群主"],
|
|
169
|
+
"hjd": ["哈尔滨市宾县宾西镇一委六组"],
|
|
170
|
+
"jzd": [],
|
|
171
|
+
"lxdh": ["15776806667"],
|
|
172
|
+
"rybq": ["重点人"],
|
|
173
|
+
"wlzh": {
|
|
174
|
+
"wx": [],
|
|
175
|
+
"qq": []
|
|
176
|
+
},
|
|
177
|
+
"gjxx": [
|
|
178
|
+
{
|
|
179
|
+
"sj": ["2024-05-28 19:00"],
|
|
180
|
+
"dd": ["网络群聊"],
|
|
181
|
+
"xw": ["组织动员"]
|
|
182
|
+
}
|
|
183
|
+
]
|
|
184
|
+
}
|
|
185
|
+
],
|
|
186
|
+
"sjtz": {
|
|
187
|
+
"xwlx": ["集体信访", "网络串联"],
|
|
188
|
+
"sqnr": ["要求政府解决房产证办理问题,明确责任主体并推动政策落实"],
|
|
189
|
+
"dktz": [],
|
|
190
|
+
"zjly": ["自筹"]
|
|
191
|
+
},
|
|
192
|
+
"czjy": {
|
|
193
|
+
"zrdw": ["哈尔滨市公安局道外分局", "信访维稳专班"],
|
|
194
|
+
"yjcs": ["提前约谈重点人员", "加强网络群组监控", "部署现场警力"]
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
20250603142141-INFO-8399134464-web_log(log:214) 127.0.0.1 [03/Jun/2025:14:18:39 +0800] "POST /chat/completion HTTP/1.1" 200 3670 "-" "python-requests/2.32.3"
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
"""
|
|
201
|
+
|
|
202
|
+
print((cjson.dumps(extract_json_from_text(xx))))
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: UTF-8 -*-
|
|
3
|
+
__author__ = 'haoyang'
|
|
4
|
+
__date__ = '2025/6/9 09:49'
|
|
5
|
+
|
|
6
|
+
import asyncio
|
|
7
|
+
import base64
|
|
8
|
+
import json
|
|
9
|
+
import mimetypes
|
|
10
|
+
import sys
|
|
11
|
+
import uuid
|
|
12
|
+
|
|
13
|
+
from ctools.util.env_config import bool_env
|
|
14
|
+
from ctools.web.aio_web_server import get_stream_resp
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ROLE:
|
|
18
|
+
ASSISTANT = "assistant"
|
|
19
|
+
USER = "user"
|
|
20
|
+
SYSTEM = "system"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def build_message(role_type: ROLE, content):
|
|
24
|
+
"""
|
|
25
|
+
快速构建消息
|
|
26
|
+
Parameters
|
|
27
|
+
----------
|
|
28
|
+
role_type 消息类型
|
|
29
|
+
content 消息内容
|
|
30
|
+
|
|
31
|
+
Returns 消息
|
|
32
|
+
-------
|
|
33
|
+
|
|
34
|
+
"""
|
|
35
|
+
return {"role": role_type, "content": content}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def build_image_message(content: str, file: bytes = None, file_path: str = None):
|
|
39
|
+
"""
|
|
40
|
+
快速构建图片消息
|
|
41
|
+
Parameters
|
|
42
|
+
----------
|
|
43
|
+
content 问题内容
|
|
44
|
+
file 图片文件
|
|
45
|
+
file_path 图片文件路径
|
|
46
|
+
|
|
47
|
+
Returns 消息
|
|
48
|
+
-------
|
|
49
|
+
|
|
50
|
+
"""
|
|
51
|
+
rep = _get_image_data_and_mime(file, file_path)
|
|
52
|
+
img_content = [{
|
|
53
|
+
"type": "image_url",
|
|
54
|
+
"image_url": {
|
|
55
|
+
"url": f'data:{rep["mime_type"]};base64,{rep["data"]}'
|
|
56
|
+
}
|
|
57
|
+
}, {
|
|
58
|
+
"type": "text",
|
|
59
|
+
"text": content
|
|
60
|
+
}]
|
|
61
|
+
return build_message(ROLE.USER, img_content)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
async def build_call_back(debug=None, request=None, SSE=True):
|
|
65
|
+
"""
|
|
66
|
+
快速构建回调函数
|
|
67
|
+
Parameters
|
|
68
|
+
----------
|
|
69
|
+
debug 是否开启调试
|
|
70
|
+
request http请求
|
|
71
|
+
|
|
72
|
+
Returns 响应对象, 消息队列, 回调函数
|
|
73
|
+
-------
|
|
74
|
+
"""
|
|
75
|
+
if not debug: debug = bool_env("LLM_DEBUG", False)
|
|
76
|
+
response = None
|
|
77
|
+
if request: response = await get_stream_resp(request)
|
|
78
|
+
call_id = uuid.uuid4()
|
|
79
|
+
message_queue = asyncio.Queue()
|
|
80
|
+
|
|
81
|
+
async def on_msg(cid, role, msg):
|
|
82
|
+
nonlocal response
|
|
83
|
+
if debug: print(msg, file=sys.__stdout__, end='', flush=True)
|
|
84
|
+
final_msg = {"id": cid, "role": role, "msg": msg}
|
|
85
|
+
await message_queue.put(msg)
|
|
86
|
+
if response:
|
|
87
|
+
if SSE:
|
|
88
|
+
await response.write(f"data: {json.dumps(final_msg)}\n\n".encode("utf-8"))
|
|
89
|
+
else:
|
|
90
|
+
await response.write(msg.encode("utf-8"))
|
|
91
|
+
|
|
92
|
+
async def on_final(cid, is_final, msg):
|
|
93
|
+
nonlocal response
|
|
94
|
+
if debug: print("\n", cid, "\n", is_final, "\n", msg, "\n", file=sys.__stdout__, flush=True)
|
|
95
|
+
if is_final:
|
|
96
|
+
await message_queue.put("[DONE]")
|
|
97
|
+
if response:
|
|
98
|
+
if SSE: await response.write(b"data: [DONE]\n\n")
|
|
99
|
+
await response.write_eof()
|
|
100
|
+
else:
|
|
101
|
+
nonlocal call_id
|
|
102
|
+
call_id = uuid.uuid4()
|
|
103
|
+
|
|
104
|
+
def get_call_id():
|
|
105
|
+
return call_id.hex
|
|
106
|
+
|
|
107
|
+
return response, message_queue, {"get_call_id": get_call_id, "get_event_msg_func": on_msg, "get_full_msg_func": on_final}
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _get_image_data_and_mime(file: bytes = None, file_path: str = None):
|
|
135
|
+
if file_path:
|
|
136
|
+
with open(file_path, "rb") as f:
|
|
137
|
+
file = f.read()
|
|
138
|
+
if not file:
|
|
139
|
+
raise ValueError("file 和 file_path 至少要提供一个")
|
|
140
|
+
mime_type = "application/octet-stream"
|
|
141
|
+
if file_path:
|
|
142
|
+
mime_type_guess, _ = mimetypes.guess_type(file_path)
|
|
143
|
+
if mime_type_guess:
|
|
144
|
+
mime_type = mime_type_guess
|
|
145
|
+
data = base64.b64encode(file).decode("utf-8")
|
|
146
|
+
return {
|
|
147
|
+
"mime_type": mime_type,
|
|
148
|
+
"data": data
|
|
149
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: UTF-8 -*-
|
|
3
|
+
__author__ = 'haoyang'
|
|
4
|
+
__date__ = '2025/6/3 14:30'
|
|
5
|
+
|
|
6
|
+
import re
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def remove_think_blocks(text: str) -> str:
|
|
10
|
+
cleaned_text = re.sub(r"<think>.*?</think>", "", text, flags=re.DOTALL)
|
|
11
|
+
return cleaned_text.strip()
|