pygpt-net 2.6.59__py3-none-any.whl → 2.6.61__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.
Files changed (91) hide show
  1. pygpt_net/CHANGELOG.txt +11 -0
  2. pygpt_net/__init__.py +3 -3
  3. pygpt_net/app.py +9 -5
  4. pygpt_net/controller/__init__.py +1 -0
  5. pygpt_net/controller/chat/common.py +115 -6
  6. pygpt_net/controller/chat/input.py +4 -1
  7. pygpt_net/controller/presets/editor.py +442 -39
  8. pygpt_net/controller/presets/presets.py +121 -6
  9. pygpt_net/controller/settings/editor.py +0 -15
  10. pygpt_net/controller/theme/markdown.py +2 -5
  11. pygpt_net/controller/ui/ui.py +4 -7
  12. pygpt_net/core/agents/custom/__init__.py +281 -0
  13. pygpt_net/core/agents/custom/debug.py +64 -0
  14. pygpt_net/core/agents/custom/factory.py +109 -0
  15. pygpt_net/core/agents/custom/graph.py +71 -0
  16. pygpt_net/core/agents/custom/llama_index/__init__.py +10 -0
  17. pygpt_net/core/agents/custom/llama_index/factory.py +100 -0
  18. pygpt_net/core/agents/custom/llama_index/router_streamer.py +106 -0
  19. pygpt_net/core/agents/custom/llama_index/runner.py +562 -0
  20. pygpt_net/core/agents/custom/llama_index/stream.py +56 -0
  21. pygpt_net/core/agents/custom/llama_index/utils.py +253 -0
  22. pygpt_net/core/agents/custom/logging.py +50 -0
  23. pygpt_net/core/agents/custom/memory.py +51 -0
  24. pygpt_net/core/agents/custom/router.py +155 -0
  25. pygpt_net/core/agents/custom/router_streamer.py +187 -0
  26. pygpt_net/core/agents/custom/runner.py +455 -0
  27. pygpt_net/core/agents/custom/schema.py +127 -0
  28. pygpt_net/core/agents/custom/utils.py +193 -0
  29. pygpt_net/core/agents/provider.py +72 -7
  30. pygpt_net/core/agents/runner.py +7 -4
  31. pygpt_net/core/agents/runners/helpers.py +1 -1
  32. pygpt_net/core/agents/runners/llama_workflow.py +3 -0
  33. pygpt_net/core/agents/runners/openai_workflow.py +8 -1
  34. pygpt_net/core/db/viewer.py +11 -5
  35. pygpt_net/{ui/widget/builder → core/node_editor}/__init__.py +2 -2
  36. pygpt_net/core/{builder → node_editor}/graph.py +28 -226
  37. pygpt_net/core/node_editor/models.py +118 -0
  38. pygpt_net/core/node_editor/types.py +78 -0
  39. pygpt_net/core/node_editor/utils.py +17 -0
  40. pygpt_net/core/presets/presets.py +216 -29
  41. pygpt_net/core/render/markdown/parser.py +0 -2
  42. pygpt_net/core/render/web/renderer.py +10 -8
  43. pygpt_net/data/config/config.json +5 -6
  44. pygpt_net/data/config/models.json +3 -3
  45. pygpt_net/data/config/settings.json +2 -38
  46. pygpt_net/data/locale/locale.de.ini +64 -1
  47. pygpt_net/data/locale/locale.en.ini +63 -4
  48. pygpt_net/data/locale/locale.es.ini +64 -1
  49. pygpt_net/data/locale/locale.fr.ini +64 -1
  50. pygpt_net/data/locale/locale.it.ini +64 -1
  51. pygpt_net/data/locale/locale.pl.ini +65 -2
  52. pygpt_net/data/locale/locale.uk.ini +64 -1
  53. pygpt_net/data/locale/locale.zh.ini +64 -1
  54. pygpt_net/data/locale/plugin.cmd_system.en.ini +62 -66
  55. pygpt_net/item/agent.py +5 -1
  56. pygpt_net/item/preset.py +19 -1
  57. pygpt_net/provider/agents/base.py +33 -2
  58. pygpt_net/provider/agents/llama_index/flow_from_schema.py +92 -0
  59. pygpt_net/provider/agents/openai/flow_from_schema.py +96 -0
  60. pygpt_net/provider/core/agent/json_file.py +11 -5
  61. pygpt_net/provider/core/config/patch.py +10 -1
  62. pygpt_net/provider/core/config/patches/patch_before_2_6_42.py +0 -6
  63. pygpt_net/tools/agent_builder/tool.py +233 -52
  64. pygpt_net/tools/agent_builder/ui/dialogs.py +172 -28
  65. pygpt_net/tools/agent_builder/ui/list.py +37 -10
  66. pygpt_net/ui/__init__.py +2 -4
  67. pygpt_net/ui/dialog/about.py +58 -38
  68. pygpt_net/ui/dialog/db.py +142 -3
  69. pygpt_net/ui/dialog/preset.py +62 -8
  70. pygpt_net/ui/layout/toolbox/presets.py +52 -16
  71. pygpt_net/ui/main.py +1 -1
  72. pygpt_net/ui/widget/dialog/db.py +0 -0
  73. pygpt_net/ui/widget/lists/preset.py +644 -60
  74. pygpt_net/{core/builder → ui/widget/node_editor}/__init__.py +2 -2
  75. pygpt_net/ui/widget/node_editor/command.py +373 -0
  76. pygpt_net/ui/widget/node_editor/config.py +157 -0
  77. pygpt_net/ui/widget/node_editor/editor.py +2070 -0
  78. pygpt_net/ui/widget/node_editor/item.py +493 -0
  79. pygpt_net/ui/widget/node_editor/node.py +1460 -0
  80. pygpt_net/ui/widget/node_editor/utils.py +17 -0
  81. pygpt_net/ui/widget/node_editor/view.py +364 -0
  82. pygpt_net/ui/widget/tabs/output.py +1 -1
  83. pygpt_net/ui/widget/textarea/input.py +2 -2
  84. pygpt_net/utils.py +114 -2
  85. {pygpt_net-2.6.59.dist-info → pygpt_net-2.6.61.dist-info}/METADATA +80 -93
  86. {pygpt_net-2.6.59.dist-info → pygpt_net-2.6.61.dist-info}/RECORD +88 -61
  87. pygpt_net/core/agents/custom.py +0 -150
  88. pygpt_net/ui/widget/builder/editor.py +0 -2001
  89. {pygpt_net-2.6.59.dist-info → pygpt_net-2.6.61.dist-info}/LICENSE +0 -0
  90. {pygpt_net-2.6.59.dist-info → pygpt_net-2.6.61.dist-info}/WHEEL +0 -0
  91. {pygpt_net-2.6.59.dist-info → pygpt_net-2.6.61.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,109 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ # ================================================== #
4
+ # This file is a part of PYGPT package #
5
+ # Website: https://pygpt.net #
6
+ # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
+ # MIT License #
8
+ # Created By : Marcin Szczygliński #
9
+ # Updated Date: 2025.09.24 23:00:00 #
10
+ # ================================================== #
11
+
12
+ from __future__ import annotations
13
+ from dataclasses import dataclass
14
+ from typing import Any, Dict, List, Optional
15
+
16
+ from agents import Agent as OpenAIAgent
17
+ from pygpt_net.item.preset import PresetItem
18
+ from pygpt_net.core.bridge import BridgeContext
19
+ from pygpt_net.provider.api.openai.agents.remote_tools import append_tools
20
+ from pygpt_net.provider.api.openai.agents.experts import get_experts
21
+
22
+ from .schema import AgentNode
23
+ from .router import build_router_instruction
24
+ from .utils import NodeRuntime
25
+
26
+
27
+ @dataclass
28
+ class BuiltAgent:
29
+ instance: OpenAIAgent
30
+ name: str
31
+ instructions: str
32
+ multi_output: bool
33
+ allowed_routes: List[str]
34
+
35
+
36
+ class AgentFactory:
37
+ """
38
+ Builds OpenAIAgent instances from AgentNode + NodeRuntime.
39
+ """
40
+ def __init__(self, window, logger) -> None:
41
+ self.window = window
42
+ self.logger = logger
43
+
44
+ def build(
45
+ self,
46
+ node: AgentNode,
47
+ node_runtime: NodeRuntime,
48
+ preset: Optional[PresetItem],
49
+ function_tools: List[dict],
50
+ force_router: bool,
51
+ friendly_map: Dict[str, str],
52
+ handoffs_enabled: bool = True,
53
+ context: Optional[BridgeContext] = None,
54
+ ) -> BuiltAgent:
55
+ # Agent name
56
+ agent_name = (node.name or "").strip() or (preset.name if preset else f"Agent {node.id}")
57
+
58
+ # Multi-output routing instruction injection
59
+ multi_output = force_router or (len(node.outputs or []) > 1)
60
+ allowed_routes = list(node.outputs or [])
61
+
62
+ instr = node_runtime.instructions
63
+ if multi_output and allowed_routes:
64
+ router_instr = build_router_instruction(agent_name, node.id, allowed_routes, friendly_map)
65
+ instr = router_instr + "\n\n" + instr if instr else router_instr
66
+
67
+ # Base kwargs
68
+ kwargs: Dict[str, Any] = {
69
+ "name": agent_name,
70
+ "instructions": instr,
71
+ "model": self.window.core.agents.provider.get_openai_model(node_runtime.model),
72
+ }
73
+
74
+ # Tools
75
+ tool_kwargs = append_tools(
76
+ tools=function_tools or [],
77
+ window=self.window,
78
+ model=node_runtime.model,
79
+ preset=preset,
80
+ allow_local_tools=node_runtime.allow_local_tools,
81
+ allow_remote_tools=node_runtime.allow_remote_tools,
82
+ )
83
+ kwargs.update(tool_kwargs)
84
+
85
+ # Experts/handoffs if any
86
+ if handoffs_enabled:
87
+ experts = get_experts(
88
+ window=self.window,
89
+ preset=preset,
90
+ verbose=False,
91
+ tools=function_tools or [],
92
+ )
93
+ if experts:
94
+ kwargs["handoffs"] = experts
95
+
96
+ # Build instance
97
+ instance = OpenAIAgent(**kwargs)
98
+ self.logger.debug(
99
+ f"Built agent {node.id} ({agent_name}), "
100
+ f"multi_output={multi_output}, routes={allowed_routes}, model={node_runtime.model.name}"
101
+ )
102
+
103
+ return BuiltAgent(
104
+ instance=instance,
105
+ name=agent_name,
106
+ instructions=instr,
107
+ multi_output=multi_output,
108
+ allowed_routes=allowed_routes,
109
+ )
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ # ================================================== #
4
+ # This file is a part of PYGPT package #
5
+ # Website: https://pygpt.net #
6
+ # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
+ # MIT License #
8
+ # Created By : Marcin Szczygliński #
9
+ # Updated Date: 2025.09.24 23:00:00 #
10
+ # ================================================== #
11
+
12
+ from __future__ import annotations
13
+ import re
14
+ from dataclasses import dataclass, field
15
+ from typing import Dict, List, Optional, Tuple
16
+
17
+ from .schema import FlowSchema, AgentNode, StartNode, EndNode, MemoryNode
18
+
19
+
20
+ @dataclass
21
+ class FlowGraph:
22
+ schema: FlowSchema
23
+ adjacency: Dict[str, List[str]] = field(default_factory=dict) # node_id -> list of node_ids
24
+ agent_to_memory: Dict[str, Optional[str]] = field(default_factory=dict) # agent_id -> mem_id or None
25
+ start_targets: List[str] = field(default_factory=list) # immediate next nodes from start
26
+ end_nodes: List[str] = field(default_factory=list) # ids of end nodes
27
+
28
+ def get_next(self, node_id: str) -> List[str]:
29
+ return self.adjacency.get(node_id, [])
30
+
31
+ def first_connected_end(self, node_id: str) -> Optional[str]:
32
+ outs = self.get_next(node_id)
33
+ for out in outs:
34
+ if out in self.schema.ends:
35
+ return out
36
+ return None
37
+
38
+ def pick_default_start_agent(self) -> Optional[str]:
39
+ """Pick lowest numeric agent id if no start is present."""
40
+ if not self.schema.agents:
41
+ return None
42
+ # Prefer numeric suffix; fallback to lexicographic
43
+ def key_fn(aid: str) -> Tuple[int, str]:
44
+ m = re.search(r"(\d+)$", aid)
45
+ if m:
46
+ try:
47
+ return (int(m.group(1)), aid)
48
+ except Exception:
49
+ pass
50
+ return (10**9, aid)
51
+
52
+ return sorted(self.schema.agents.keys(), key=key_fn)[0]
53
+
54
+
55
+ def build_graph(fs: FlowSchema) -> FlowGraph:
56
+ g = FlowGraph(schema=fs)
57
+ # adjacency from agents
58
+ for aid, anode in fs.agents.items():
59
+ g.adjacency[aid] = list(anode.outputs or [])
60
+ g.agent_to_memory[aid] = anode.memory_out
61
+
62
+ # adjacency from start nodes
63
+ g.end_nodes = list(fs.ends.keys())
64
+ g.start_targets = []
65
+ if fs.starts:
66
+ # By spec there can be multiple start nodes; we concatenate their outputs in order found
67
+ for sid, snode in fs.starts.items():
68
+ g.adjacency[sid] = list(snode.outputs or [])
69
+ g.start_targets.extend(snode.outputs or [])
70
+
71
+ return g
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ # ================================================== #
4
+ # This file is a part of PYGPT package #
5
+ # Website: https://pygpt.net #
6
+ # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
+ # MIT License #
8
+ # Created By : Marcin Szczygliński #
9
+ # Updated Date: 2025.09.24 23:00:00 #
10
+ # ================================================== #
@@ -0,0 +1,100 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ # ================================================== #
4
+ # This file is a part of PYGPT package #
5
+ # Website: https://pygpt.net #
6
+ # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
+ # MIT License #
8
+ # Created By : Marcin Szczygliński #
9
+ # Updated Date: 2025.09.25 14:00:00 #
10
+ # ================================================== #
11
+
12
+ from __future__ import annotations
13
+ from dataclasses import dataclass
14
+ from typing import Any, Dict, List
15
+
16
+ from llama_index.core.agent.workflow import ReActAgent, FunctionAgent
17
+
18
+ from ..schema import AgentNode
19
+ from ..router import build_router_instruction
20
+ from .utils import NodeRuntime, coerce_li_tools
21
+
22
+
23
+ @dataclass
24
+ class BuiltAgentLI:
25
+ instance: Any
26
+ name: str
27
+ instructions: str
28
+ multi_output: bool
29
+ allowed_routes: List[str]
30
+
31
+
32
+ class AgentFactoryLI:
33
+ """
34
+ Build LlamaIndex ReActAgent/FunctionAgent from AgentNode + NodeRuntime and explicit LLM/tools.
35
+ """
36
+ def __init__(self, window, logger) -> None:
37
+ self.window = window
38
+ self.logger = logger
39
+
40
+ def build(
41
+ self,
42
+ *,
43
+ node: AgentNode,
44
+ node_runtime: NodeRuntime,
45
+ llm: Any, # LLM instance (z appki lub resolve_llm)
46
+ tools: List[Any], # BaseTool list
47
+ friendly_map: Dict[str, Any],
48
+ force_router: bool = False,
49
+ chat_history: List[Any] = None,
50
+ max_iterations: int = 10,
51
+ ) -> BuiltAgentLI:
52
+ agent_name = (node.name or "").strip() or f"Agent {node.id}"
53
+
54
+ multi_output = force_router or (len(node.outputs or []) > 1)
55
+ allowed_routes = list(node.outputs or [])
56
+
57
+ instr = node_runtime.instructions
58
+ if multi_output and allowed_routes:
59
+ router_instr = build_router_instruction(agent_name, node.id, allowed_routes, friendly_map)
60
+ instr = router_instr + "\n\n" + instr if instr else router_instr
61
+
62
+ node_tools = tools if (node_runtime.allow_local_tools or node_runtime.allow_remote_tools) else []
63
+
64
+ # Prefer FunctionAgent if the underlying LLM supports function-calling (recommended by LI).
65
+ # This yields more direct compliance with system_prompt for simple single-output tasks.
66
+ is_fc_model = False
67
+ try:
68
+ is_fc_model = bool(getattr(getattr(llm, "metadata", None), "is_function_calling_model", False))
69
+ except Exception:
70
+ is_fc_model = False
71
+
72
+ if multi_output:
73
+ agent_cls = FunctionAgent # routers: keep JSON compliance
74
+ else:
75
+ agent_cls = FunctionAgent if is_fc_model else ReActAgent
76
+
77
+ kwargs: Dict[str, Any] = {
78
+ "name": agent_name,
79
+ "system_prompt": instr,
80
+ "llm": llm,
81
+ "chat_history": chat_history or [],
82
+ "max_iterations": int(max_iterations),
83
+ # Provide a short description to reinforce the agent's purpose (if role present).
84
+ "description": (node_runtime.role or agent_name),
85
+ }
86
+ if node_tools:
87
+ kwargs["tools"] = coerce_li_tools(node_tools)
88
+
89
+ instance = agent_cls(**kwargs)
90
+ self.logger.debug(
91
+ f"[li] Built agent {node.id} ({agent_name}), multi_output={multi_output}, "
92
+ f"routes={allowed_routes}, agent_cls={agent_cls.__name__}"
93
+ )
94
+ return BuiltAgentLI(
95
+ instance=instance,
96
+ name=agent_name,
97
+ instructions=instr,
98
+ multi_output=multi_output,
99
+ allowed_routes=allowed_routes,
100
+ )
@@ -0,0 +1,106 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ # ================================================== #
4
+ # This file is a part of PYGPT package #
5
+ # Website: https://pygpt.net #
6
+ # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
+ # MIT License #
8
+ # Created By : Marcin Szczygliński #
9
+ # Updated Date: 2025.09.24 23:00:00 #
10
+ # ================================================== #
11
+
12
+ from __future__ import annotations
13
+ import json
14
+ import re
15
+ from typing import Optional
16
+
17
+
18
+ class DelayedRouterStreamerLI:
19
+ """Collect raw JSON stream (no UI)."""
20
+ def __init__(self):
21
+ self._raw = ""
22
+ def reset(self):
23
+ self._raw = ""
24
+ def handle_delta(self, delta: str):
25
+ self._raw += delta or ""
26
+ @property
27
+ def buffer(self) -> str:
28
+ return self._raw
29
+
30
+
31
+ class RealtimeRouterStreamerLI:
32
+ """
33
+ Stream only JSON 'content' string incrementally.
34
+ handle_delta(delta) -> returns decoded content suffix to emit (may be '').
35
+ """
36
+ CONTENT_PATTERN = re.compile(r'"content"\s*:\s*"')
37
+
38
+ def __init__(self):
39
+ self._raw = ""
40
+ self._content_started = False
41
+ self._content_closed = False
42
+ self._content_start_idx = -1
43
+ self._content_raw = ""
44
+ self._content_decoded = ""
45
+
46
+ def reset(self):
47
+ self._raw = ""
48
+ self._content_started = False
49
+ self._content_closed = False
50
+ self._content_start_idx = -1
51
+ self._content_raw = ""
52
+ self._content_decoded = ""
53
+
54
+ def handle_delta(self, delta: str) -> str:
55
+ if not delta:
56
+ return ""
57
+ self._raw += delta
58
+ if not self._content_started:
59
+ m = self.CONTENT_PATTERN.search(self._raw)
60
+ if m:
61
+ self._content_started = True
62
+ self._content_start_idx = m.end()
63
+ else:
64
+ return ""
65
+ if self._content_started and not self._content_closed:
66
+ return self._process()
67
+ return ""
68
+
69
+ def _process(self) -> str:
70
+ sub = self._raw[self._content_start_idx:]
71
+ close_idx = self._find_unescaped_quote(sub)
72
+ if close_idx is not None:
73
+ portion = sub[:close_idx]
74
+ self._content_closed = True
75
+ else:
76
+ portion = sub
77
+ new_raw_piece = portion[len(self._content_raw):]
78
+ if not new_raw_piece:
79
+ return ""
80
+ self._content_raw += new_raw_piece
81
+ try:
82
+ decoded_full = json.loads(f'"{self._content_raw}"')
83
+ new_suffix = decoded_full[len(self._content_decoded):]
84
+ self._content_decoded = decoded_full
85
+ return new_suffix
86
+ except Exception:
87
+ return ""
88
+
89
+ @staticmethod
90
+ def _find_unescaped_quote(s: str) -> Optional[int]:
91
+ i = 0
92
+ while i < len(s):
93
+ if s[i] == '"':
94
+ j = i - 1
95
+ bs = 0
96
+ while j >= 0 and s[j] == '\\':
97
+ bs += 1
98
+ j -= 1
99
+ if bs % 2 == 0:
100
+ return i
101
+ i += 1
102
+ return None
103
+
104
+ @property
105
+ def buffer(self) -> str:
106
+ return self._raw