pengent 1.2.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.
- pengent/__init__.py +0 -0
- pengent/agents/__init__.py +9 -0
- pengent/agents/agent_base.py +665 -0
- pengent/agents/basic/agent_chat.py +42 -0
- pengent/agents/basic/agent_coder.py +49 -0
- pengent/core/artifacts/artifact.py +66 -0
- pengent/core/artifacts/artifact_service/__init__.py +10 -0
- pengent/core/artifacts/artifact_service/base_artifact_service.py +166 -0
- pengent/core/artifacts/artifact_service/in_memory_artfact_service.py +155 -0
- pengent/core/artifacts/artifact_service/storage_artifact_service.py +281 -0
- pengent/core/message_memory/__init__.py +4 -0
- pengent/core/message_memory/history_message_memory.py +30 -0
- pengent/core/message_memory/message_memory.py +80 -0
- pengent/core/message_memory/retrieval_message_memory.py +142 -0
- pengent/core/message_memory/storage/__init__.py +7 -0
- pengent/core/message_memory/storage/file_memory_storage.py +19 -0
- pengent/core/message_memory/storage/memory_storage_interface.py +14 -0
- pengent/core/message_memory/storage/redis_memory_storage.py +29 -0
- pengent/core/message_memory/summary_message_memory.py +42 -0
- pengent/core/message_memory/vector/faissy_store.py +124 -0
- pengent/core/message_memory/vector/qdrant_store.py +98 -0
- pengent/core/message_memory/vector/vectore_store_factory.py +69 -0
- pengent/core/message_memory/vector/vectore_store_interface.py +21 -0
- pengent/core/runners/__init__.py +5 -0
- pengent/core/runners/runner.py +77 -0
- pengent/core/sessions/__init__.py +11 -0
- pengent/core/sessions/session.py +27 -0
- pengent/core/sessions/session_service/database_session_service.py +290 -0
- pengent/core/sessions/session_service/in_memory_session_service.py +101 -0
- pengent/core/sessions/session_service/redis_session_service.py +165 -0
- pengent/core/sessions/session_service/session_service_base.py +105 -0
- pengent/core/sessions/session_service/storage_session_service.py +175 -0
- pengent/db/base.py +109 -0
- pengent/db/models/messages_orm.py +37 -0
- pengent/db/models/session_orm.py +25 -0
- pengent/errors/already_exists_error.py +14 -0
- pengent/lib/__init__.py +10 -0
- pengent/lib/common.py +64 -0
- pengent/lib/custom_logger.py +130 -0
- pengent/lib/singleton.py +24 -0
- pengent/llm/__init__.py +14 -0
- pengent/llm/batch/llm_batch_anthropic.py +431 -0
- pengent/llm/batch/llm_batch_base.py +338 -0
- pengent/llm/batch/llm_batch_openai.py +433 -0
- pengent/llm/llm_client_anthropic.py +175 -0
- pengent/llm/llm_client_base.py +140 -0
- pengent/llm/llm_client_factory.py +66 -0
- pengent/llm/llm_client_gemini.py +215 -0
- pengent/llm/llm_client_gguf.py +140 -0
- pengent/llm/llm_client_open_router.py +199 -0
- pengent/llm/llm_client_openai.py +180 -0
- pengent/policies/__init__.py +19 -0
- pengent/policies/actions/__init__.py +7 -0
- pengent/policies/actions/action_base.py +71 -0
- pengent/policies/polices/all_match_policy.py +25 -0
- pengent/policies/polices/best_score_policy.py +32 -0
- pengent/policies/polices/first_match_policy.py +17 -0
- pengent/policies/polices/policy_base.py +38 -0
- pengent/policies/polices/threshold_policy.py +134 -0
- pengent/policies/rules/__init__.py +25 -0
- pengent/policies/rules/generic_line_rule.py +82 -0
- pengent/policies/rules/identifier_rule.py +133 -0
- pengent/policies/rules/japanese_intent_rule.py +248 -0
- pengent/policies/rules/keyword_ex_rule.py +110 -0
- pengent/policies/rules/keyword_rule.py +53 -0
- pengent/policies/rules/max_len_rule.py +21 -0
- pengent/policies/rules/min_len_rule.py +21 -0
- pengent/policies/rules/regex_rule.py +27 -0
- pengent/policies/rules/rule_base.py +61 -0
- pengent/policies/rules/status_equals_rule.py +17 -0
- pengent/policies/rules/value_exists_rule.py +16 -0
- pengent/templates/template_factory.py +62 -0
- pengent/tools/__init__.py +15 -0
- pengent/tools/api/chat/api_rocket_chat.py +164 -0
- pengent/tools/api/chat/api_slack.py +79 -0
- pengent/tools/api/cloud/api_cloud_base.py +111 -0
- pengent/tools/api/cloud/runpod/api_runpod.py +77 -0
- pengent/tools/api/git/api_git_base.py +429 -0
- pengent/tools/api/git/api_gitea.py +664 -0
- pengent/tools/api/git/api_github.py +101 -0
- pengent/tools/api/google/api_google_calender.py +246 -0
- pengent/tools/api/google/api_google_docs.py +31 -0
- pengent/tools/api/google/api_google_drive.py +187 -0
- pengent/tools/api/google/api_google_mail.py +46 -0
- pengent/tools/api/google/api_google_slides.py +1061 -0
- pengent/tools/api/news/api_news_data.py +56 -0
- pengent/tools/api/notion/api_notion.py +392 -0
- pengent/tools/api/post/api_qiita.py +286 -0
- pengent/tools/api/post/api_wiki_js.py +203 -0
- pengent/tools/api/project/redmine/__init__.py +5 -0
- pengent/tools/api/project/redmine/api_redmine.py +585 -0
- pengent/tools/api/report/api_report_pdf.py +182 -0
- pengent/tools/api/search/api_brave_search.py +52 -0
- pengent/tools/api/search/api_google_web_search.py +57 -0
- pengent/tools/api/search/api_read_webpage.py +54 -0
- pengent/tools/api/services/api_hotpepper_gourmet.py +36 -0
- pengent/tools/api/services/open_weather.py +81 -0
- pengent/tools/mcp/__init__.py +3 -0
- pengent/tools/mcp/mcp_client.py +70 -0
- pengent/tools/mcp/mcp_data.py +130 -0
- pengent/tools/mcp/mcp_tool_package.py +166 -0
- pengent/tools/tool_utils.py +116 -0
- pengent/tools/tools/coder/__init__.py +5 -0
- pengent/tools/tools/coder/tool_run_code.py +121 -0
- pengent/tools/tools/coder/tool_run_docker_code.py +144 -0
- pengent/tools/tools/genaral/files_tool.py +79 -0
- pengent/tools/tools/git/tool_giter_basic.py +252 -0
- pengent/tools/tools/issues/redmine_tool.py +249 -0
- pengent/tools/tools/location/locatoion_tools.py +44 -0
- pengent/tools/tools/post/tool_qiita.py +77 -0
- pengent/tools/tools/post/tool_wiki_post.py +93 -0
- pengent/tools/tools/search/tool_web_search.py +60 -0
- pengent/tools/tools/utility/tool_news.py +28 -0
- pengent/type/agent/__init__.py +6 -0
- pengent/type/agent/agent_enum.py +167 -0
- pengent/type/llm/llm_message.py +493 -0
- pengent/type/llm/llm_response.py +158 -0
- pengent/type/llm/llm_type_enum.py +47 -0
- pengent/type/rag.py +103 -0
- pengent/type/rag_block.py +84 -0
- pengent/type/tool/tool_enum.py +323 -0
- pengent/utility/categorizer/__init__.py +0 -0
- pengent/utility/categorizer/categorize.py +102 -0
- pengent/utility/chunker/block/block_chunker.py +116 -0
- pengent/utility/chunker/block/html_parser.py +109 -0
- pengent/utility/chunker/block/markdown_parser.py +143 -0
- pengent/utility/chunker/block/pdf_parser.py +128 -0
- pengent/utility/chunker/chunker_base.py +70 -0
- pengent/utility/chunker/chunker_factory.py +77 -0
- pengent/utility/chunker/faq/faq_analyzer.py +210 -0
- pengent/utility/chunker/faq/pdf_faq_parser.py +89 -0
- pengent/utility/chunker/faq/text_faq_parser.py +33 -0
- pengent/utility/chunker/text/text_chunker.py +93 -0
- pengent/utility/embed/embed_base.py +28 -0
- pengent/utility/embed/embed_with_local.py +38 -0
- pengent/utility/embed/embed_with_openai.py +35 -0
- pengent/utility/reader/html_async_reader.py +96 -0
- pengent/utility/reader/html_reader.py +97 -0
- pengent/utility/reader/request_async_reader.py +22 -0
- pengent/utility/reader/request_reader.py +21 -0
- pengent/utility/storage/__init__.py +7 -0
- pengent/utility/storage/local_storage.py +130 -0
- pengent/utility/storage/storage_base.py +121 -0
- pengent/utility/summarize.py +30 -0
- pengent/workers/__init__.py +9 -0
- pengent/workers/basic/worker_call_tool.py +403 -0
- pengent/workers/basic/worker_greet.py +41 -0
- pengent/workers/worker_base.py +295 -0
- pengent-1.2.1.dist-info/METADATA +52 -0
- pengent-1.2.1.dist-info/RECORD +153 -0
- pengent-1.2.1.dist-info/WHEEL +5 -0
- pengent-1.2.1.dist-info/licenses/LICENSE +9 -0
- pengent-1.2.1.dist-info/top_level.txt +1 -0
pengent/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,665 @@
|
|
|
1
|
+
import uuid
|
|
2
|
+
import time
|
|
3
|
+
import json
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
|
|
6
|
+
from typing import Union, Optional
|
|
7
|
+
from ..llm.llm_client_base import LLMClientBase
|
|
8
|
+
|
|
9
|
+
# メッセージメモリ及びタスクの状態管理、ツール管理をするためのモジュール
|
|
10
|
+
from ..core.sessions.session import Session
|
|
11
|
+
from ..type.llm.llm_message import LLMMessage, LLMMessageTool
|
|
12
|
+
from ..type.llm.llm_response import LLMResponse
|
|
13
|
+
from ..type.agent.agent_enum import (
|
|
14
|
+
AgentSendInput,
|
|
15
|
+
AgentSendOutput,
|
|
16
|
+
ExecutionContext,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
from ..tools import ToolUnion, ToolBase, ToolUtils
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
from ..lib.common import strip_json_fence
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class AgentBase:
|
|
26
|
+
"""
|
|
27
|
+
全エージェントの共通基底クラス(personal)
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init__(
|
|
31
|
+
self,
|
|
32
|
+
name: str,
|
|
33
|
+
llm_client: LLMClientBase = None,
|
|
34
|
+
params: dict = None,
|
|
35
|
+
tools: list[ToolUnion] = None,
|
|
36
|
+
logger=None,
|
|
37
|
+
):
|
|
38
|
+
"""
|
|
39
|
+
コンストラクタ
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
name (str): エージェント名
|
|
43
|
+
llm_client (LLMClientBase, optional): LLMクライアント
|
|
44
|
+
params (dict, optional): エージェントパラメータ
|
|
45
|
+
tools (list[ToolUnion], optional):ツールリスト.
|
|
46
|
+
logger (optional): ロガーインスタンス.
|
|
47
|
+
"""
|
|
48
|
+
self.name = name
|
|
49
|
+
self.llm_client = llm_client
|
|
50
|
+
self.params = params if params else {}
|
|
51
|
+
if logger:
|
|
52
|
+
self.logger = logger
|
|
53
|
+
else:
|
|
54
|
+
from ..lib import get_logger
|
|
55
|
+
|
|
56
|
+
self.logger = get_logger()
|
|
57
|
+
|
|
58
|
+
self.tools: list[ToolUnion] = tools if tools else []
|
|
59
|
+
self._tool_map: dict[str, ToolBase] = {}
|
|
60
|
+
|
|
61
|
+
# コールバックを設定するメソッド
|
|
62
|
+
self.message_callback = None
|
|
63
|
+
|
|
64
|
+
def set_llm_client(self, llm_client: LLMClientBase):
|
|
65
|
+
"""LLMクライアントを設定する"""
|
|
66
|
+
self.llm_client = llm_client
|
|
67
|
+
|
|
68
|
+
def add_tool(self, tool: ToolUnion):
|
|
69
|
+
"""エージェントにツールを追加するメソッド"""
|
|
70
|
+
if not self.tools:
|
|
71
|
+
self.tools = []
|
|
72
|
+
self.tools.append(tool)
|
|
73
|
+
|
|
74
|
+
def set_system_prompt(self, system_prompt: str):
|
|
75
|
+
"""システムプロンプトを設定する(上書き)
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
system_prompt (str): システムプロンプト
|
|
79
|
+
"""
|
|
80
|
+
self.params["system_prompt"] = system_prompt
|
|
81
|
+
|
|
82
|
+
# ----------------------------
|
|
83
|
+
# Main Method
|
|
84
|
+
|
|
85
|
+
def run(
|
|
86
|
+
self,
|
|
87
|
+
session: Session,
|
|
88
|
+
input: Union[str, dict, AgentSendInput] = None,
|
|
89
|
+
ctx: Optional[ExecutionContext] = None,
|
|
90
|
+
**kwargs,
|
|
91
|
+
) -> AgentSendOutput:
|
|
92
|
+
"""
|
|
93
|
+
エージェントを起動する
|
|
94
|
+
"""
|
|
95
|
+
self.logger.info(f"{self.name} is Running.")
|
|
96
|
+
|
|
97
|
+
# LLMクライアントにツールを設定する
|
|
98
|
+
# (_tool_mapがあればキャッシュされているのでスキップ)
|
|
99
|
+
if self.tools and not self._tool_map:
|
|
100
|
+
tool_bases: list[ToolBase] = ToolUtils.normalize_tools(self.tools)
|
|
101
|
+
self._tool_map = {tool.name: tool for tool in tool_bases}
|
|
102
|
+
self.logger.debug("set tools to llm client.")
|
|
103
|
+
self.llm_client.tools = [t.dump() for t in tool_bases]
|
|
104
|
+
|
|
105
|
+
# LLMクライアントにシステムプロンプトを指定する
|
|
106
|
+
self.llm_client.system_prompt = self.system_prompt
|
|
107
|
+
|
|
108
|
+
return self.send(input, session, ctx, **kwargs)
|
|
109
|
+
|
|
110
|
+
def send(
|
|
111
|
+
self,
|
|
112
|
+
input: Union[str, dict, AgentSendInput] = None,
|
|
113
|
+
session: Session = None,
|
|
114
|
+
ctx: Optional[ExecutionContext] = None,
|
|
115
|
+
**kwargs,
|
|
116
|
+
):
|
|
117
|
+
"""
|
|
118
|
+
エージェントにメッセージを送信するメソッド
|
|
119
|
+
"""
|
|
120
|
+
try:
|
|
121
|
+
if session is None and ctx is not None:
|
|
122
|
+
raise ValueError("Either session or ctx must be provided.")
|
|
123
|
+
if session is None:
|
|
124
|
+
# セッションが存在しない婆は作成する
|
|
125
|
+
session = Session(
|
|
126
|
+
session_id=uuid.uuid4().hex,
|
|
127
|
+
user_id=f"gestuser-{int(datetime.now().timestamp())}",
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
if not ctx:
|
|
131
|
+
ctx = ExecutionContext.create(session=session)
|
|
132
|
+
|
|
133
|
+
self.logger.debug(f"send message start. input: {input}")
|
|
134
|
+
if input and isinstance(input, dict):
|
|
135
|
+
input = AgentSendInput(**input)
|
|
136
|
+
elif input and isinstance(input, str):
|
|
137
|
+
input = AgentSendInput(content=input)
|
|
138
|
+
input.set_session(session)
|
|
139
|
+
|
|
140
|
+
# ユーザープロンプトの自動生成機能が有効か確認する
|
|
141
|
+
is_make_user_prompt = self.get_value_for_kwargs(
|
|
142
|
+
"is_make_user_prompt", False, **kwargs
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
if is_make_user_prompt:
|
|
146
|
+
self.logger.debug("make user prompt.")
|
|
147
|
+
text = self.make_user_prompt(**input.to_dict())
|
|
148
|
+
else:
|
|
149
|
+
if input.content and isinstance(input.content, str):
|
|
150
|
+
text = input.content.strip()
|
|
151
|
+
elif input.content and isinstance(input.content, list):
|
|
152
|
+
for con in input.content:
|
|
153
|
+
text = con.text.strip()
|
|
154
|
+
|
|
155
|
+
self.logger.debug(f"send message text: {text}")
|
|
156
|
+
|
|
157
|
+
# メッセージの送信処理について
|
|
158
|
+
messages = []
|
|
159
|
+
_message = LLMMessage.create_user_message(text)
|
|
160
|
+
messages.append(_message)
|
|
161
|
+
response = self.llm_client.request(messages=session.events.get() + messages)
|
|
162
|
+
return self.receive_message(session, response, messages, context=ctx)
|
|
163
|
+
|
|
164
|
+
except Exception as e:
|
|
165
|
+
self.logger.exception(f"send message Error: {e}")
|
|
166
|
+
raise e
|
|
167
|
+
finally:
|
|
168
|
+
self.logger.debug("send message end.")
|
|
169
|
+
|
|
170
|
+
def receive_message(
|
|
171
|
+
self,
|
|
172
|
+
session: Session,
|
|
173
|
+
response: LLMResponse,
|
|
174
|
+
messages: list[LLMMessage],
|
|
175
|
+
*,
|
|
176
|
+
context: Optional[ExecutionContext] = None,
|
|
177
|
+
):
|
|
178
|
+
"""
|
|
179
|
+
エージェントが受信したメッセージを処理するメソッド
|
|
180
|
+
"""
|
|
181
|
+
# ツールが呼ばれていないか確認する
|
|
182
|
+
tools = response.get_tools()
|
|
183
|
+
if tools:
|
|
184
|
+
response = self.handle_tools_call(session, messages, tools, context=context)
|
|
185
|
+
|
|
186
|
+
# メッセージを確認する
|
|
187
|
+
if not response.is_message():
|
|
188
|
+
# メッセージがない場合は、エラーを返す
|
|
189
|
+
self.logger.error(f"receive_message Error: {response}")
|
|
190
|
+
raise ValueError("no message.")
|
|
191
|
+
|
|
192
|
+
response, output = self._with_retry(session, response, messages)
|
|
193
|
+
_message = LLMMessage.create_assistant_message(output.message)
|
|
194
|
+
messages.append(_message)
|
|
195
|
+
|
|
196
|
+
self.logger.debug(f"receive_message end. {output}")
|
|
197
|
+
return self.handler_message(messages, output, context=context)
|
|
198
|
+
|
|
199
|
+
def _register_tools_call(
|
|
200
|
+
self, messages: list[LLMMessage], tools: list[LLMMessageTool]
|
|
201
|
+
):
|
|
202
|
+
"""Functionsコールをメッセージに登録するメソッド"""
|
|
203
|
+
_message = LLMMessage.create_tools_call(tools)
|
|
204
|
+
messages.append(_message)
|
|
205
|
+
|
|
206
|
+
def _exec_tool_call_response(
|
|
207
|
+
self,
|
|
208
|
+
session: Session,
|
|
209
|
+
result: Union[str, dict, list],
|
|
210
|
+
messages: list[LLMMessage],
|
|
211
|
+
tool: LLMMessageTool,
|
|
212
|
+
):
|
|
213
|
+
if isinstance(result, list) or isinstance(result, dict):
|
|
214
|
+
result = json.dumps(result)
|
|
215
|
+
_message = LLMMessage.create_tools_result(tool_call_id=tool.id, content=result)
|
|
216
|
+
messages.append(_message) #
|
|
217
|
+
response = self.llm_client.request(messages=session.events.get() + messages)
|
|
218
|
+
return response
|
|
219
|
+
|
|
220
|
+
def _exec_tool_call(
|
|
221
|
+
self,
|
|
222
|
+
session: Session,
|
|
223
|
+
messages: list[LLMMessage],
|
|
224
|
+
tool: LLMMessageTool,
|
|
225
|
+
*,
|
|
226
|
+
context: Optional[ExecutionContext] = None,
|
|
227
|
+
):
|
|
228
|
+
"""
|
|
229
|
+
ツールコールを実行するメソッド
|
|
230
|
+
"""
|
|
231
|
+
self.logger.debug(f"tool: {tool.function.name} {tool.function.arguments}")
|
|
232
|
+
|
|
233
|
+
# ツールを実行するためのMapを持つ
|
|
234
|
+
name = tool.function.name
|
|
235
|
+
tool_base = self._tool_map.get(name)
|
|
236
|
+
if not tool_base:
|
|
237
|
+
result = json.dumps({"error": f"Tool Not Found in Agent: {name}"})
|
|
238
|
+
else:
|
|
239
|
+
result = ToolUtils.execute_tool(
|
|
240
|
+
tool_base,
|
|
241
|
+
tool.function.arguments,
|
|
242
|
+
context=context,
|
|
243
|
+
)
|
|
244
|
+
return self._exec_tool_call_response(session, result, messages, tool)
|
|
245
|
+
|
|
246
|
+
def handle_tools_call(
|
|
247
|
+
self,
|
|
248
|
+
session: Session,
|
|
249
|
+
messages: list[LLMMessage],
|
|
250
|
+
tools: list[LLMMessageTool],
|
|
251
|
+
*,
|
|
252
|
+
context: Optional[ExecutionContext] = None,
|
|
253
|
+
):
|
|
254
|
+
self.logger.debug(f"handle_tools_call receive tools: {tools}")
|
|
255
|
+
self._register_tools_call(messages, tools)
|
|
256
|
+
for tool in tools:
|
|
257
|
+
response = self._exec_tool_call(session, messages, tool, context=context)
|
|
258
|
+
|
|
259
|
+
return response
|
|
260
|
+
|
|
261
|
+
def handler_message(
|
|
262
|
+
self,
|
|
263
|
+
messages: list[LLMMessage],
|
|
264
|
+
output: AgentSendOutput,
|
|
265
|
+
*,
|
|
266
|
+
context: Optional[ExecutionContext] = None,
|
|
267
|
+
) -> AgentSendOutput:
|
|
268
|
+
"""
|
|
269
|
+
エージェントからの応答を処理するメソッド
|
|
270
|
+
"""
|
|
271
|
+
output.events_messages = messages
|
|
272
|
+
self._output = output
|
|
273
|
+
|
|
274
|
+
if context.get_state_delta():
|
|
275
|
+
output.set_context("state_delta", context.get_state_delta())
|
|
276
|
+
|
|
277
|
+
if self.message_callback:
|
|
278
|
+
# メッセージコールバックが設定されている場合は呼び出す
|
|
279
|
+
self.logger.debug("call message callback.")
|
|
280
|
+
self.message_callback(messages, output)
|
|
281
|
+
|
|
282
|
+
return output
|
|
283
|
+
|
|
284
|
+
def _with_retry(
|
|
285
|
+
self,
|
|
286
|
+
session: Session,
|
|
287
|
+
response: LLMResponse,
|
|
288
|
+
messages: list[LLMMessage],
|
|
289
|
+
) -> tuple[LLMResponse, AgentSendOutput]:
|
|
290
|
+
"""LLMメッセージリクエストをパース(リトライ/再送信)するための中間メソッド"""
|
|
291
|
+
# リトライの回数とリトライ間隔
|
|
292
|
+
max_retries = self.params.get("retry_max_count", 3)
|
|
293
|
+
retry_delay_sec = self.params.get("retry_delay_sec", 10)
|
|
294
|
+
|
|
295
|
+
for attempt in range(max_retries):
|
|
296
|
+
try:
|
|
297
|
+
output = self._parse_response_content(response)
|
|
298
|
+
return response, output # 成功
|
|
299
|
+
except Exception as e:
|
|
300
|
+
error_detail = str(e)
|
|
301
|
+
self.logger.error(
|
|
302
|
+
f"Parse failed on attempt {attempt + 1}: {error_detail}"
|
|
303
|
+
)
|
|
304
|
+
messages.append(
|
|
305
|
+
LLMMessage.create_assistant_message(response.get_message())
|
|
306
|
+
)
|
|
307
|
+
messages.append(
|
|
308
|
+
LLMMessage.create_user_message(
|
|
309
|
+
content=(
|
|
310
|
+
"**Failed to parse the response.**\n"
|
|
311
|
+
"Please make sure the output format is correct "
|
|
312
|
+
"and includes all required keys.\n"
|
|
313
|
+
f"(Detail: {error_detail})"
|
|
314
|
+
)
|
|
315
|
+
)
|
|
316
|
+
)
|
|
317
|
+
if attempt < max_retries - 1:
|
|
318
|
+
time.sleep(retry_delay_sec)
|
|
319
|
+
response = self.llm_client.request(
|
|
320
|
+
messages=session.events.get() + messages
|
|
321
|
+
)
|
|
322
|
+
else:
|
|
323
|
+
raise ValueError(
|
|
324
|
+
f"Parsing failed after {max_retries} attempts. "
|
|
325
|
+
f"Last error: {error_detail}"
|
|
326
|
+
) from e
|
|
327
|
+
|
|
328
|
+
def _parse_response_content(self, response: LLMResponse) -> AgentSendOutput:
|
|
329
|
+
"""パースを実行するメソッド"""
|
|
330
|
+
self.logger.debug("_parse_response_content start.")
|
|
331
|
+
self.logger.debug(
|
|
332
|
+
f"format: {self.format} is_contents_type: {self.is_contents_type}"
|
|
333
|
+
)
|
|
334
|
+
content_text = response.get_message()
|
|
335
|
+
if self.is_contents_type:
|
|
336
|
+
# 先頭に出力タイプ
|
|
337
|
+
lines = content_text.splitlines()
|
|
338
|
+
self.logger.info(
|
|
339
|
+
f"contents_type(Head 1 Line):{lines[0].strip() if lines else ''}"
|
|
340
|
+
)
|
|
341
|
+
content_text = "\n".join(lines[1:]) if len(lines) > 1 else ""
|
|
342
|
+
|
|
343
|
+
self.logger.debug(f"content_text:\n{content_text}")
|
|
344
|
+
|
|
345
|
+
if self.params.get("callback_parse"): # 独自のパース関数で実行する場合
|
|
346
|
+
self.logger.debug("Using custom call_parse method.")
|
|
347
|
+
return self.params["callback_parse"](content_text)
|
|
348
|
+
|
|
349
|
+
# 通常パースを実行する
|
|
350
|
+
if self.format == "application/json":
|
|
351
|
+
self.logger.debug("Parsing as JSON.")
|
|
352
|
+
content_text = strip_json_fence(content_text)
|
|
353
|
+
content_json = self.llm_client.parse_json(content_text)
|
|
354
|
+
output = AgentSendOutput()
|
|
355
|
+
output.message = content_json.get("message", "JSON Response No Message")
|
|
356
|
+
output.set_context("json", content_json)
|
|
357
|
+
return output
|
|
358
|
+
else:
|
|
359
|
+
self.logger.debug("no parse text.")
|
|
360
|
+
return AgentSendOutput(message=content_text)
|
|
361
|
+
|
|
362
|
+
# ----------------------------
|
|
363
|
+
# params
|
|
364
|
+
|
|
365
|
+
@property
|
|
366
|
+
def format(self) -> str:
|
|
367
|
+
return self.params.get("format", "text/plain")
|
|
368
|
+
|
|
369
|
+
@format.setter
|
|
370
|
+
def format(self, value: str):
|
|
371
|
+
self.params["format"] = value
|
|
372
|
+
|
|
373
|
+
@property
|
|
374
|
+
def call_agents(self) -> list[dict]:
|
|
375
|
+
"""呼び出せる関数・ツールのリスト"""
|
|
376
|
+
return self.params.get("call_agents", None)
|
|
377
|
+
|
|
378
|
+
@call_agents.setter
|
|
379
|
+
def call_agents(self, value: list[dict]):
|
|
380
|
+
self.params["call_agents"] = value
|
|
381
|
+
|
|
382
|
+
def dump_call_agents(self):
|
|
383
|
+
"""呼び出すことのできるエージェントJSON形式でダンプするメソッド(プロンプト用)"""
|
|
384
|
+
return json.dumps(self.call_agents, indent=2, ensure_ascii=False)
|
|
385
|
+
|
|
386
|
+
@property
|
|
387
|
+
def system_prompt(self) -> str:
|
|
388
|
+
"""システムプロンプトを取得する
|
|
389
|
+
|
|
390
|
+
Notes:
|
|
391
|
+
- paramsにsystem_promptが設定されていれば、優先して使用する。
|
|
392
|
+
- それ以外の場合は、make_system_promptメソッドを使用して生成する。
|
|
393
|
+
"""
|
|
394
|
+
if self.params.get("system_prompt", None):
|
|
395
|
+
return self.params["system_prompt"]
|
|
396
|
+
else:
|
|
397
|
+
return self.make_system_prompt()
|
|
398
|
+
|
|
399
|
+
def update_param(self, key: str, value: any):
|
|
400
|
+
"""
|
|
401
|
+
エージェントのパラメータを更新するメソッド
|
|
402
|
+
|
|
403
|
+
Parameters:
|
|
404
|
+
key (str): パラメータのキー
|
|
405
|
+
value (any): パラメータの値
|
|
406
|
+
"""
|
|
407
|
+
self.params[key] = value
|
|
408
|
+
|
|
409
|
+
def clear_param(self, key: str):
|
|
410
|
+
"""
|
|
411
|
+
エージェントのパラメータを更新するメソッド
|
|
412
|
+
|
|
413
|
+
Parameters:
|
|
414
|
+
key (str): パラメータのキー
|
|
415
|
+
"""
|
|
416
|
+
if key in self.params:
|
|
417
|
+
del self.params[key]
|
|
418
|
+
|
|
419
|
+
def get_value_for_session_stat(
|
|
420
|
+
self, session: Session, key, default=None, **kwargs
|
|
421
|
+
) -> any:
|
|
422
|
+
val = session.stat.get(key, None)
|
|
423
|
+
if val:
|
|
424
|
+
return val
|
|
425
|
+
return kwargs.get(key, self.params.get(key, default))
|
|
426
|
+
|
|
427
|
+
def get_value_for_kwargs(self, key, default=None, **kwargs) -> any:
|
|
428
|
+
return kwargs.get(key, self.params.get(key, default))
|
|
429
|
+
|
|
430
|
+
def get_value_for_params(
|
|
431
|
+
self,
|
|
432
|
+
key,
|
|
433
|
+
default=None,
|
|
434
|
+
) -> any:
|
|
435
|
+
return self.params.get(key, default)
|
|
436
|
+
|
|
437
|
+
# -----------------------------
|
|
438
|
+
# Agentのオプション設定項目
|
|
439
|
+
|
|
440
|
+
@property
|
|
441
|
+
def is_disable_system_pronpt(self) -> bool:
|
|
442
|
+
"""システムプロンプトを禁止する"""
|
|
443
|
+
return self.params.get("is_disable_system_pronpt", False)
|
|
444
|
+
|
|
445
|
+
@is_disable_system_pronpt.setter
|
|
446
|
+
def is_disable_system_pronpt(self, value: bool):
|
|
447
|
+
self.params["is_disable_system_pronpt"] = value
|
|
448
|
+
|
|
449
|
+
@property
|
|
450
|
+
def is_contents_type(self) -> bool:
|
|
451
|
+
"""受信メッセージの先頭(1行目)に出力タイプを含むかどうか"""
|
|
452
|
+
return self.params.get("is_contents_type", False)
|
|
453
|
+
|
|
454
|
+
@is_contents_type.setter
|
|
455
|
+
def is_contents_type(self, value: bool):
|
|
456
|
+
self.params["is_contents_type"] = value
|
|
457
|
+
|
|
458
|
+
@property
|
|
459
|
+
def is_make_user_prompt(self) -> bool:
|
|
460
|
+
"""ユーザープロンプトを自動生成するかどうか"""
|
|
461
|
+
return self.params.get("is_make_user_prompt", False)
|
|
462
|
+
|
|
463
|
+
@is_make_user_prompt.setter
|
|
464
|
+
def is_make_user_prompt(self, value: bool):
|
|
465
|
+
self.params["is_make_user_prompt"] = value
|
|
466
|
+
|
|
467
|
+
@property
|
|
468
|
+
def is_outoput_example(self) -> bool:
|
|
469
|
+
"""システムプロンプトに出力のサンプルを自動的に含めるかどうか
|
|
470
|
+
Notes:
|
|
471
|
+
- 手動でexampleを設定した場合は、is_outoput_exampleは無効となる。
|
|
472
|
+
"""
|
|
473
|
+
return self.params.get("is_outoput_example", True)
|
|
474
|
+
|
|
475
|
+
@is_outoput_example.setter
|
|
476
|
+
def is_outoput_example(self, value: bool):
|
|
477
|
+
self.params["is_outoput_example"] = value
|
|
478
|
+
|
|
479
|
+
@property
|
|
480
|
+
def is_outoput_schema(self) -> bool:
|
|
481
|
+
"""
|
|
482
|
+
システムプロンプトに出力するJSONスキーマを含むかどうか(JSON形式)
|
|
483
|
+
|
|
484
|
+
Notes:
|
|
485
|
+
- 手動でschemaを設定した場合は、is_outoput_schemaは無効となる。
|
|
486
|
+
- トークンが増えるためデフォルトではFalseに設定されている。
|
|
487
|
+
"""
|
|
488
|
+
return self.params.get("is_outoput_schema", False)
|
|
489
|
+
|
|
490
|
+
@is_outoput_schema.setter
|
|
491
|
+
def is_outoput_schema(self, value: bool):
|
|
492
|
+
self.params["is_outoput_schema"] = value
|
|
493
|
+
|
|
494
|
+
@property
|
|
495
|
+
def is_add_outoput_constraints(self) -> bool:
|
|
496
|
+
"""システムプロンプトに出力フォーマットにあわせた制約を自動的に含めるかどうか"""
|
|
497
|
+
return self.params.get("is_add_outoput_constraints", True)
|
|
498
|
+
|
|
499
|
+
@is_add_outoput_constraints.setter
|
|
500
|
+
def is_add_outoput_constraints(self, value: bool):
|
|
501
|
+
self.params["is_add_outoput_constraints"] = value
|
|
502
|
+
|
|
503
|
+
# ----------------------------
|
|
504
|
+
# system_prompt
|
|
505
|
+
def make_system_prompt(self) -> str:
|
|
506
|
+
"""
|
|
507
|
+
システムプロンプトを生成するメソッド
|
|
508
|
+
"""
|
|
509
|
+
if self.is_disable_system_pronpt:
|
|
510
|
+
return None
|
|
511
|
+
|
|
512
|
+
system_prompt = ""
|
|
513
|
+
|
|
514
|
+
role = self.get_value_for_params("role", "you are an assistant")
|
|
515
|
+
system_prompt += f"## Role:\n{role}\n\n"
|
|
516
|
+
|
|
517
|
+
goal = self.get_value_for_params("goal")
|
|
518
|
+
if goal:
|
|
519
|
+
system_prompt += "## Goal\nあなたの役割は次の通りです。\n"
|
|
520
|
+
system_prompt += f"目的: {goal}\n\n"
|
|
521
|
+
|
|
522
|
+
# 出力フォーマット
|
|
523
|
+
system_prompt += "## Output\n\n"
|
|
524
|
+
|
|
525
|
+
if self.format == "text/plain":
|
|
526
|
+
system_prompt += (
|
|
527
|
+
"期待される出力は自然な日本語の文章です。構造化は不要です。\n\n"
|
|
528
|
+
)
|
|
529
|
+
elif self.format == "application/json":
|
|
530
|
+
system_prompt += "期待される出力は純粋な JSON オブジェクトです。\n"
|
|
531
|
+
system_prompt += "コードブロック(``` など)は出力しないこと\n\n"
|
|
532
|
+
elif self.format == "text/markdown":
|
|
533
|
+
system_prompt += "期待される出力はMarkdown形式の構造化された文章です。\n"
|
|
534
|
+
system_prompt += (
|
|
535
|
+
"見出し(#, ##, ###など)やリスト、表、"
|
|
536
|
+
"コードブロックなどを適切に使用すること。\n"
|
|
537
|
+
)
|
|
538
|
+
system_prompt += (
|
|
539
|
+
"読みやすさと構造化を意識し、自然な日本語で記述すること。\n\n"
|
|
540
|
+
)
|
|
541
|
+
|
|
542
|
+
# 出力スキーマを出力する
|
|
543
|
+
schema = self.get_value_for_params("schema", None)
|
|
544
|
+
if schema:
|
|
545
|
+
system_prompt += f"### Schema:\n````\n{schema}\n````\n\n"
|
|
546
|
+
|
|
547
|
+
# 出力サンプルを出力する
|
|
548
|
+
example = self.get_value_for_params("example", None)
|
|
549
|
+
if example:
|
|
550
|
+
system_prompt += "### Example:n"
|
|
551
|
+
if self.is_contents_type:
|
|
552
|
+
system_prompt += f"{self.format}\n"
|
|
553
|
+
system_prompt += f"{example}\n\n"
|
|
554
|
+
|
|
555
|
+
# 呼び出せるツール・関数を制御する
|
|
556
|
+
if self.call_agents:
|
|
557
|
+
system_prompt += "## Enable Calling Agents\n"
|
|
558
|
+
system_prompt += "必要な時は次のエージェントを呼び出すことができます:\n"
|
|
559
|
+
system_prompt += f"````\n{self.dump_call_agents()}\n````\n\n"
|
|
560
|
+
|
|
561
|
+
# SOPに関する指示
|
|
562
|
+
sop_list = []
|
|
563
|
+
sop_list += self.get_value_for_params("sop", [])
|
|
564
|
+
if sop_list:
|
|
565
|
+
system_prompt += "## SOP (ordered)\n"
|
|
566
|
+
system_prompt += "\n".join(f"- {s}" for s in sop_list) + "\n\n"
|
|
567
|
+
|
|
568
|
+
# 実行ルールを明記(順序厳守/ブロック時の振る舞い)
|
|
569
|
+
system_prompt += (
|
|
570
|
+
"### 実行ルール\n"
|
|
571
|
+
"- 上から順に1→2 → ... と実行すること"
|
|
572
|
+
"(指示のない順番変更・スキップ・並行実行は禁止)。\n"
|
|
573
|
+
"- 実行不能な場合は 理由 または不足情報(最大3つ)のみ返し、"
|
|
574
|
+
"推測で進めないこと。\n"
|
|
575
|
+
"- 上記に従わない出力は無効とみなし破棄する。\n\n"
|
|
576
|
+
)
|
|
577
|
+
|
|
578
|
+
# 制約に関する指示
|
|
579
|
+
constraints = []
|
|
580
|
+
constraints += self.get_value_for_params("constraints", [])
|
|
581
|
+
if isinstance(constraints, str):
|
|
582
|
+
constraints = [constraints]
|
|
583
|
+
|
|
584
|
+
if constraints:
|
|
585
|
+
constraints += [
|
|
586
|
+
"**上記の指示を守れない場合は、出力は破棄され、再実行されます。**"
|
|
587
|
+
]
|
|
588
|
+
system_prompt += "## Constraints:\n"
|
|
589
|
+
system_prompt += (
|
|
590
|
+
f"{chr(10).join([f'- {constraint}' for constraint in constraints])}\n\n"
|
|
591
|
+
)
|
|
592
|
+
|
|
593
|
+
return system_prompt
|
|
594
|
+
|
|
595
|
+
# ----------------------------
|
|
596
|
+
# user_prompt
|
|
597
|
+
|
|
598
|
+
def make_user_prompt(self, **kwargs) -> str:
|
|
599
|
+
user_prompt = ""
|
|
600
|
+
content = kwargs.get("content", None)
|
|
601
|
+
# 本文を出力する
|
|
602
|
+
if content:
|
|
603
|
+
if isinstance(content, str):
|
|
604
|
+
_content = content.strip()
|
|
605
|
+
elif isinstance(content, list):
|
|
606
|
+
for con in content:
|
|
607
|
+
_content = con.text.strip()
|
|
608
|
+
user_prompt += f"{_content}"
|
|
609
|
+
|
|
610
|
+
state = self.get_value_for_kwargs("state", None, **kwargs)
|
|
611
|
+
summary = self.get_value_for_kwargs("summary", None, **kwargs)
|
|
612
|
+
prior_info = self.get_value_for_kwargs("prior_info", None, **kwargs)
|
|
613
|
+
template: str = self.get_value_for_kwargs("template", None, **kwargs)
|
|
614
|
+
|
|
615
|
+
if not state and not summary and not prior_info and not template:
|
|
616
|
+
return user_prompt
|
|
617
|
+
|
|
618
|
+
if content:
|
|
619
|
+
user_prompt += "\n\n---\n"
|
|
620
|
+
|
|
621
|
+
if state:
|
|
622
|
+
user_prompt += f"## State:\n```\n{json.dumps(state, indent=2)}\n```\n\n"
|
|
623
|
+
|
|
624
|
+
if summary:
|
|
625
|
+
user_prompt += f"## Summary:\n{summary}\n\n"
|
|
626
|
+
|
|
627
|
+
if prior_info:
|
|
628
|
+
lines = [
|
|
629
|
+
f"- {key}:{chr(10).join(f'・{v}' for v in value)}"
|
|
630
|
+
if isinstance(value, list)
|
|
631
|
+
else f"{key}:{value}"
|
|
632
|
+
for key, value in prior_info.items()
|
|
633
|
+
]
|
|
634
|
+
prior = "\n".join(lines)
|
|
635
|
+
user_prompt += f"## Prior Info:\n{prior}\n\n"
|
|
636
|
+
|
|
637
|
+
if template:
|
|
638
|
+
if template.startswith("file://"):
|
|
639
|
+
template_file = template.replace("file://", "")
|
|
640
|
+
with open(template_file, "r", encoding="utf-8") as f:
|
|
641
|
+
_template = f.read()
|
|
642
|
+
else:
|
|
643
|
+
_template = template
|
|
644
|
+
|
|
645
|
+
user_prompt += f"## Template:\n```\n{_template}\n```\n\n"
|
|
646
|
+
|
|
647
|
+
return user_prompt
|
|
648
|
+
|
|
649
|
+
def to_dict(self) -> dict:
|
|
650
|
+
"""
|
|
651
|
+
エージェント情報を辞書形式に変換します。
|
|
652
|
+
"""
|
|
653
|
+
data = {
|
|
654
|
+
"class": self.__class__.__name__,
|
|
655
|
+
"name": self.name,
|
|
656
|
+
"params": self.params,
|
|
657
|
+
# "tools": self.tools,
|
|
658
|
+
"call_agents": self.call_agents,
|
|
659
|
+
"system_prompt": self.system_prompt,
|
|
660
|
+
"llm_model_name": self.llm_client.model_name if self.llm_client else None,
|
|
661
|
+
"llm_temperature": self.llm_client.temperature if self.llm_client else None,
|
|
662
|
+
"llm_llm_type": self.llm_client.llm_type if self.llm_client else None,
|
|
663
|
+
"llm_config": self.llm_client.config if self.llm_client else None,
|
|
664
|
+
}
|
|
665
|
+
return data
|