agentengine-sdk-python 0.6.9__py3-none-any.whl → 0.7.0__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.
- {agentengine_sdk_python-0.6.9.dist-info → agentengine_sdk_python-0.7.0.dist-info}/METADATA +6 -4
- {agentengine_sdk_python-0.6.9.dist-info → agentengine_sdk_python-0.7.0.dist-info}/RECORD +123 -128
- ksadk/api/client.py +19 -0
- ksadk/cli/cmd_create.py +34 -7
- ksadk/cli/cmd_hermes.py +6 -2
- ksadk/cli/cmd_invoke.py +208 -41
- ksadk/cli/cmd_mcp.py +6 -1
- ksadk/cli/cmd_openclaw.py +6 -2
- ksadk/cli/cmd_run.py +13 -6
- ksadk/cli/network_options.py +5 -3
- ksadk/configs/env_registry.py +1 -0
- ksadk/conversations/message_projection.py +269 -0
- ksadk/conversations/model_context.py +11 -1
- ksadk/conversations/runtime.py +81 -13
- ksadk/deployment/base.py +1 -1
- ksadk/deployment/providers/serverless.py +18 -8
- ksadk/memory/adk/resilient_session_service.py +268 -0
- ksadk/memory/adk/short_term_memory.py +21 -6
- ksadk/runners/adk_runner.py +974 -115
- ksadk/runners/langchain_runner.py +219 -7
- ksadk/runners/langgraph_runner.py +145 -12
- ksadk/runners/remote_runner.py +143 -18
- ksadk/server/app.py +148 -6
- ksadk/server/static/assets/{ArtifactsPanel-C0meuOdi.js → ArtifactsPanel-D-SSn2ee.js} +1 -1
- ksadk/server/static/assets/{CodeBlock-BbI4HPtJ.js → CodeBlock-P16VOLYi.js} +1 -1
- ksadk/server/static/assets/{MathMessageMarkdown-DMLQYoLp.js → MathMessageMarkdown-CdGiQWHR.js} +1 -1
- ksadk/server/static/assets/{MermaidBlock-DCR8WZ17.js → MermaidBlock-IOcal8cm.js} +6 -6
- ksadk/server/static/assets/{NativeTerminalPanel-CKUvWhUz.js → NativeTerminalPanel-DGSDWsei.js} +1 -1
- ksadk/server/static/assets/{arc-bj2rZnP5.js → arc-BA13sHc9.js} +1 -1
- ksadk/server/static/assets/architecture-7EHR7CIX-BvUlsA6l.js +1 -0
- ksadk/server/static/assets/{architectureDiagram-3BPJPVTR-2u1ltDIj.js → architectureDiagram-3BPJPVTR-DzJzgVoL.js} +1 -1
- ksadk/server/static/assets/{blockDiagram-GPEHLZMM-D6fvdK20.js → blockDiagram-GPEHLZMM-UTdVcZhn.js} +1 -1
- ksadk/server/static/assets/{c4Diagram-AAUBKEIU-BK82L5n5.js → c4Diagram-AAUBKEIU-Dix0GD4m.js} +1 -1
- ksadk/server/static/assets/channel-CTuydARX.js +1 -0
- ksadk/server/static/assets/{chunk-2J33WTMH-DLVdkeyQ.js → chunk-2J33WTMH-C2JxuZ42.js} +1 -1
- ksadk/server/static/assets/{chunk-4BX2VUAB-BbwsYcbW.js → chunk-4BX2VUAB-Be57QPgD.js} +1 -1
- ksadk/server/static/assets/{chunk-55IACEB6-DOairxzw.js → chunk-55IACEB6-WjSuy4nv.js} +1 -1
- ksadk/server/static/assets/{chunk-727SXJPM-hCO487gs.js → chunk-727SXJPM-BOovP-b5.js} +1 -1
- ksadk/server/static/assets/{chunk-AQP2D5EJ-XU8vDOuG.js → chunk-AQP2D5EJ-DOmZNmWj.js} +1 -1
- ksadk/server/static/assets/{chunk-FMBD7UC4-PrapyfMu.js → chunk-FMBD7UC4-DzwlodkU.js} +1 -1
- ksadk/server/static/assets/{chunk-ND2GUHAM-CboKhTZ3.js → chunk-ND2GUHAM-DMqAikEU.js} +1 -1
- ksadk/server/static/assets/chunk-QZHKN3VN-BnCydivS.js +1 -0
- ksadk/server/static/assets/classDiagram-4FO5ZUOK-BNHg45Be.js +1 -0
- ksadk/server/static/assets/classDiagram-v2-Q7XG4LA2-BNHg45Be.js +1 -0
- ksadk/server/static/assets/{cose-bilkent-S5V4N54A-_N8bJ-EU.js → cose-bilkent-S5V4N54A-Zhy2GBnP.js} +1 -1
- ksadk/server/static/assets/{dagre-BM42HDAG-FoDlo5J2.js → dagre-BM42HDAG-CbKrtIUz.js} +1 -1
- ksadk/server/static/assets/{diagram-2AECGRRQ-BBp6ebBw.js → diagram-2AECGRRQ-BRApgQRK.js} +1 -1
- ksadk/server/static/assets/{diagram-5GNKFQAL-CqOvG1PR.js → diagram-5GNKFQAL-D0xbnDiK.js} +1 -1
- ksadk/server/static/assets/{diagram-KO2AKTUF-O9T3t6ba.js → diagram-KO2AKTUF-CVugVI9L.js} +1 -1
- ksadk/server/static/assets/{diagram-LMA3HP47-CGG8ekBO.js → diagram-LMA3HP47-DozUGgee.js} +1 -1
- ksadk/server/static/assets/{diagram-OG6HWLK6-4BmH1EEg.js → diagram-OG6HWLK6-ZOYsKlru.js} +1 -1
- ksadk/server/static/assets/{dist-BmuTWcm7.js → dist-2hInLS_J.js} +1 -1
- ksadk/server/static/assets/{dist-BMKM66B4.js → dist-8EEyGgDx.js} +1 -1
- ksadk/server/static/assets/{dist-Cp1_jKwf.js → dist-8_SfK1IP.js} +1 -1
- ksadk/server/static/assets/{dist-CZ6C3x8B.js → dist-B0NP_Z0z.js} +1 -1
- ksadk/server/static/assets/{dist-DLeM1OCK.js → dist-BIJVDMuB.js} +1 -1
- ksadk/server/static/assets/{dist-Cwhk4lwI.js → dist-BmlMPsCc.js} +1 -1
- ksadk/server/static/assets/{dist-D99wf6_X.js → dist-BrCcQ47T.js} +1 -1
- ksadk/server/static/assets/{dist-CWiTDN_V.js → dist-BssVmjIo.js} +1 -1
- ksadk/server/static/assets/{dist-BVzcWa6D.js → dist-C3HIIaN5.js} +1 -1
- ksadk/server/static/assets/{dist-DM0rEQPk.js → dist-CPBtqRTW.js} +1 -1
- ksadk/server/static/assets/{dist-BkJjNih3.js → dist-CkEKJFtk.js} +1 -1
- ksadk/server/static/assets/{dist-B67uF2uK.js → dist-D-JMdWPQ.js} +1 -1
- ksadk/server/static/assets/{dist-DI-oVAXw.js → dist-D-xOD048.js} +1 -1
- ksadk/server/static/assets/{dist-6kSZvCZh.js → dist-D57wnGWd.js} +1 -1
- ksadk/server/static/assets/{dist-Bk1l05Ni.js → dist-DL_NKc_z.js} +1 -1
- ksadk/server/static/assets/{dist-DbaH3p5h.js → dist-DMezvBGJ.js} +1 -1
- ksadk/server/static/assets/{dist-CiZHB6ZW.js → dist-DmFaq_Iy.js} +1 -1
- ksadk/server/static/assets/{dist-DLMjL01k.js → dist-Dp0k7bWI.js} +1 -1
- ksadk/server/static/assets/{dist-DCpZrjaK.js → dist-DpKtt4Bo.js} +1 -1
- ksadk/server/static/assets/{dist-D5JGcxBx.js → dist-DyOkZmze.js} +1 -1
- ksadk/server/static/assets/{dist-C2DKRFVK.js → dist-DyWsb78Q.js} +1 -1
- ksadk/server/static/assets/{dist-S4klkK5W.js → dist-gX6GOLKq.js} +1 -1
- ksadk/server/static/assets/{erDiagram-TEJ5UH35-oJhaci7S.js → erDiagram-TEJ5UH35-Bk-9bgE5.js} +1 -1
- ksadk/server/static/assets/eventmodeling-FCH6USID-DsAgWnlf.js +1 -0
- ksadk/server/static/assets/{flowDiagram-I6XJVG4X-DtTmUan-.js → flowDiagram-I6XJVG4X-Dn9kZVuZ.js} +1 -1
- ksadk/server/static/assets/{ganttDiagram-6RSMTGT7-BSsb_eij.js → ganttDiagram-6RSMTGT7-BGxXlJqm.js} +1 -1
- ksadk/server/static/assets/gitGraph-WXDBUCRP-BIao9Yjg.js +1 -0
- ksadk/server/static/assets/{gitGraphDiagram-PVQCEYII-BR5s6Pws.js → gitGraphDiagram-PVQCEYII-C7Q0X-wi.js} +1 -1
- ksadk/server/static/assets/{index-BH7-pC0B.js → index-C00Lwg3L.js} +6 -6
- ksadk/server/static/assets/info-J43DQDTF-CyipqGxU.js +1 -0
- ksadk/server/static/assets/{infoDiagram-5YYISTIA-DYPc5cvh.js → infoDiagram-5YYISTIA-BO_CSqq5.js} +1 -1
- ksadk/server/static/assets/{ishikawaDiagram-YF4QCWOH-MoBdALDg.js → ishikawaDiagram-YF4QCWOH-eYLPLd46.js} +1 -1
- ksadk/server/static/assets/{journeyDiagram-JHISSGLW-DgLiQT8l.js → journeyDiagram-JHISSGLW-De9geSb0.js} +1 -1
- ksadk/server/static/assets/{kanban-definition-UN3LZRKU-CsFx112F.js → kanban-definition-UN3LZRKU-Cs80kQto.js} +1 -1
- ksadk/server/static/assets/{katex-DRsiwpM2.js → katex-DSrCFD8n.js} +1 -1
- ksadk/server/static/assets/{linear-TNRQ5Orv.js → linear-CN9sbKTF.js} +1 -1
- ksadk/server/static/assets/{mermaid-parser.core-D2eMVOgo.js → mermaid-parser.core-DlgSBU8l.js} +2 -2
- ksadk/server/static/assets/{mindmap-definition-RKZ34NQL-BtByYFhL.js → mindmap-definition-RKZ34NQL-9afz7Oug.js} +1 -1
- ksadk/server/static/assets/packet-YPE3B663-CGkyRxlg.js +1 -0
- ksadk/server/static/assets/pie-LRSECV5Y-M0uWAf4n.js +1 -0
- ksadk/server/static/assets/{pieDiagram-4H26LBE5-DPnuCB8c.js → pieDiagram-4H26LBE5-DPv_VstN.js} +1 -1
- ksadk/server/static/assets/{quadrantDiagram-W4KKPZXB-miFpCPv4.js → quadrantDiagram-W4KKPZXB-o77wZ1XK.js} +1 -1
- ksadk/server/static/assets/radar-GUYGQ44K-Bdbm24-x.js +1 -0
- ksadk/server/static/assets/{requirementDiagram-4Y6WPE33-mjB_yYkW.js → requirementDiagram-4Y6WPE33-BYVw81VC.js} +1 -1
- ksadk/server/static/assets/{sankeyDiagram-5OEKKPKP-D7hSlZZZ.js → sankeyDiagram-5OEKKPKP-B7nZFMKT.js} +1 -1
- ksadk/server/static/assets/{sequenceDiagram-3UESZ5HK-D9Cx6hC3.js → sequenceDiagram-3UESZ5HK-BGRRrFw5.js} +1 -1
- ksadk/server/static/assets/{stateDiagram-AJRCARHV-D_zrg47o.js → stateDiagram-AJRCARHV-BUfHCoPs.js} +1 -1
- ksadk/server/static/assets/stateDiagram-v2-BHNVJYJU-8xBN_gLM.js +1 -0
- ksadk/server/static/assets/{timeline-definition-PNZ67QCA-CwSec0Wa.js → timeline-definition-PNZ67QCA-FbCTGrf9.js} +1 -1
- ksadk/server/static/assets/treeView-BLDUP644-npMo0ak1.js +1 -0
- ksadk/server/static/assets/treemap-LRROVOQU-CHdzrrdo.js +1 -0
- ksadk/server/static/assets/{vennDiagram-CIIHVFJN-agCVobiF.js → vennDiagram-CIIHVFJN-DWgwtPsJ.js} +1 -1
- ksadk/server/static/assets/wardley-L42UT6IY-BEQz7wyy.js +1 -0
- ksadk/server/static/assets/{wardleyDiagram-YWT4CUSO-TmrlhWNt.js → wardleyDiagram-YWT4CUSO-2x8G-tTM.js} +1 -1
- ksadk/server/static/assets/{xychartDiagram-2RQKCTM6-DV71V-ah.js → xychartDiagram-2RQKCTM6-Drma3AYl.js} +1 -1
- ksadk/server/static/index.html +1 -1
- ksadk/sessions/__init__.py +17 -21
- ksadk/sessions/continuity.py +28 -4
- ksadk/sessions/postgres_service.py +77 -14
- ksadk/sessions/resilience.py +52 -0
- ksadk/sessions/resilient.py +360 -0
- ksadk/toolsets/__init__.py +2 -0
- ksadk/toolsets/workspace.py +49 -10
- ksadk/tracing/setup.py +63 -0
- ksadk/tui/__init__.py +22 -7
- ksadk/tui/loop.py +1877 -0
- ksadk/tui/stream_render.py +68 -0
- ksadk/version.py +1 -1
- ksadk/server/static/assets/architecture-7EHR7CIX-Cr6Xz1_c.js +0 -1
- ksadk/server/static/assets/channel-BSSOwFIb.js +0 -1
- ksadk/server/static/assets/chunk-QZHKN3VN-XXpdiwUL.js +0 -1
- ksadk/server/static/assets/classDiagram-4FO5ZUOK-C5HvGiyK.js +0 -1
- ksadk/server/static/assets/classDiagram-v2-Q7XG4LA2-C5HvGiyK.js +0 -1
- ksadk/server/static/assets/eventmodeling-FCH6USID-CIgwHJLL.js +0 -1
- ksadk/server/static/assets/gitGraph-WXDBUCRP-BVAJ7uo0.js +0 -1
- ksadk/server/static/assets/info-J43DQDTF-MT80V4ge.js +0 -1
- ksadk/server/static/assets/packet-YPE3B663-BLKhbXZB.js +0 -1
- ksadk/server/static/assets/pie-LRSECV5Y-DFOikjSY.js +0 -1
- ksadk/server/static/assets/radar-GUYGQ44K-095b1Ikw.js +0 -1
- ksadk/server/static/assets/stateDiagram-v2-BHNVJYJU-J9kCb0nC.js +0 -1
- ksadk/server/static/assets/treeView-BLDUP644-yinfJEY6.js +0 -1
- ksadk/server/static/assets/treemap-LRROVOQU-DosUqN1Q.js +0 -1
- ksadk/server/static/assets/wardley-L42UT6IY-Bvp7-5uH.js +0 -1
- ksadk/tui/app.py +0 -400
- ksadk/tui/clipboard.py +0 -100
- ksadk/tui/widgets/__init__.py +0 -22
- ksadk/tui/widgets/assistant.py +0 -89
- ksadk/tui/widgets/base.py +0 -54
- ksadk/tui/widgets/chat_input.py +0 -246
- ksadk/tui/widgets/history.py +0 -156
- ksadk/tui/widgets/system.py +0 -87
- ksadk/tui/widgets/thinking.py +0 -110
- ksadk/tui/widgets/tool_call.py +0 -161
- ksadk/tui/widgets/user.py +0 -37
- {agentengine_sdk_python-0.6.9.dist-info → agentengine_sdk_python-0.7.0.dist-info}/WHEEL +0 -0
- {agentengine_sdk_python-0.6.9.dist-info → agentengine_sdk_python-0.7.0.dist-info}/entry_points.txt +0 -0
- {agentengine_sdk_python-0.6.9.dist-info → agentengine_sdk_python-0.7.0.dist-info}/licenses/LICENSE +0 -0
- {agentengine_sdk_python-0.6.9.dist-info → agentengine_sdk_python-0.7.0.dist-info}/top_level.txt +0 -0
|
@@ -110,9 +110,66 @@ class LangChainRunner(BaseRunner):
|
|
|
110
110
|
|
|
111
111
|
accumulated_text = ""
|
|
112
112
|
last_chunk: Any = None
|
|
113
|
+
final_output_text = ""
|
|
114
|
+
emitted_non_text_event = False
|
|
115
|
+
message_snapshots: dict[str, str] = {}
|
|
113
116
|
|
|
114
117
|
try:
|
|
115
|
-
if
|
|
118
|
+
if self._should_stream_events(payload):
|
|
119
|
+
kwargs = self._build_optional_call_kwargs(
|
|
120
|
+
self._agent.astream_events,
|
|
121
|
+
config=config,
|
|
122
|
+
context=native_context,
|
|
123
|
+
)
|
|
124
|
+
kwargs["version"] = "v2"
|
|
125
|
+
async for event in self._agent.astream_events(payload, **kwargs):
|
|
126
|
+
if not isinstance(event, dict):
|
|
127
|
+
continue
|
|
128
|
+
event_kind = event.get("event", "")
|
|
129
|
+
data = event.get("data") or {}
|
|
130
|
+
|
|
131
|
+
if event_kind == "on_chat_model_stream":
|
|
132
|
+
chunk = data.get("chunk") if isinstance(data, dict) else None
|
|
133
|
+
if chunk is None:
|
|
134
|
+
continue
|
|
135
|
+
last_chunk = chunk
|
|
136
|
+
delta, chunk_type = self._extract_chunk(chunk)
|
|
137
|
+
if delta:
|
|
138
|
+
if chunk_type == "text":
|
|
139
|
+
accumulated_text += delta
|
|
140
|
+
else:
|
|
141
|
+
emitted_non_text_event = True
|
|
142
|
+
yield {"delta": delta, "type": chunk_type}
|
|
143
|
+
elif event_kind == "on_tool_start":
|
|
144
|
+
emitted_non_text_event = True
|
|
145
|
+
yield {
|
|
146
|
+
"type": "tool_call",
|
|
147
|
+
"tool_name": event.get("name", "unknown"),
|
|
148
|
+
"tool_args": data.get("input", {}) if isinstance(data, dict) else {},
|
|
149
|
+
"run_id": event.get("run_id"),
|
|
150
|
+
}
|
|
151
|
+
elif event_kind == "on_tool_end":
|
|
152
|
+
emitted_non_text_event = True
|
|
153
|
+
tool_output = data.get("output", "") if isinstance(data, dict) else ""
|
|
154
|
+
yield {
|
|
155
|
+
"type": "tool_result",
|
|
156
|
+
"tool_name": event.get("name", "unknown"),
|
|
157
|
+
"tool_args": data.get("input", {}) if isinstance(data, dict) else {},
|
|
158
|
+
"tool_output": (
|
|
159
|
+
tool_output
|
|
160
|
+
if isinstance(tool_output, dict)
|
|
161
|
+
else (str(tool_output) if tool_output else "")
|
|
162
|
+
),
|
|
163
|
+
"run_id": event.get("run_id"),
|
|
164
|
+
}
|
|
165
|
+
elif event_kind == "on_chain_end":
|
|
166
|
+
output = data.get("output") if isinstance(data, dict) else None
|
|
167
|
+
extracted_output = self._extract_recognized_output(output)
|
|
168
|
+
if extracted_output:
|
|
169
|
+
final_output_text = extracted_output
|
|
170
|
+
if self._extract_usage(output):
|
|
171
|
+
last_chunk = output
|
|
172
|
+
elif hasattr(self._agent, "astream"):
|
|
116
173
|
kwargs = self._build_optional_call_kwargs(
|
|
117
174
|
self._agent.astream,
|
|
118
175
|
config=config,
|
|
@@ -120,7 +177,15 @@ class LangChainRunner(BaseRunner):
|
|
|
120
177
|
)
|
|
121
178
|
async for chunk in self._agent.astream(payload, **kwargs):
|
|
122
179
|
last_chunk = chunk
|
|
123
|
-
|
|
180
|
+
message_state = self._extract_message_state(chunk)
|
|
181
|
+
if message_state:
|
|
182
|
+
content, message_key = message_state
|
|
183
|
+
previous = message_snapshots.get(message_key, "")
|
|
184
|
+
delta = self._snapshot_delta(content, previous)
|
|
185
|
+
message_snapshots[message_key] = content
|
|
186
|
+
chunk_type = "text"
|
|
187
|
+
else:
|
|
188
|
+
delta, chunk_type = self._extract_chunk(chunk)
|
|
124
189
|
if delta:
|
|
125
190
|
accumulated_text += delta
|
|
126
191
|
yield {"delta": delta, "type": chunk_type}
|
|
@@ -132,14 +197,32 @@ class LangChainRunner(BaseRunner):
|
|
|
132
197
|
)
|
|
133
198
|
for chunk in self._agent.stream(payload, **kwargs):
|
|
134
199
|
last_chunk = chunk
|
|
135
|
-
|
|
200
|
+
message_state = self._extract_message_state(chunk)
|
|
201
|
+
if message_state:
|
|
202
|
+
content, message_key = message_state
|
|
203
|
+
previous = message_snapshots.get(message_key, "")
|
|
204
|
+
delta = self._snapshot_delta(content, previous)
|
|
205
|
+
message_snapshots[message_key] = content
|
|
206
|
+
chunk_type = "text"
|
|
207
|
+
else:
|
|
208
|
+
delta, chunk_type = self._extract_chunk(chunk)
|
|
136
209
|
if delta:
|
|
137
210
|
accumulated_text += delta
|
|
138
211
|
yield {"delta": delta, "type": chunk_type}
|
|
139
212
|
except Exception as exc:
|
|
140
|
-
|
|
213
|
+
logger.warning("LangChain stream failed: %s", exc)
|
|
141
214
|
|
|
142
215
|
if not accumulated_text:
|
|
216
|
+
if final_output_text or emitted_non_text_event:
|
|
217
|
+
final_chunk = {"output": final_output_text, "type": "final"}
|
|
218
|
+
usage = self._extract_usage(last_chunk)
|
|
219
|
+
if usage:
|
|
220
|
+
final_chunk["usage"] = usage
|
|
221
|
+
last_usage = self._extract_last_usage(last_chunk)
|
|
222
|
+
if last_usage:
|
|
223
|
+
final_chunk.setdefault("metadata", {})["last_usage"] = last_usage
|
|
224
|
+
yield final_chunk
|
|
225
|
+
return
|
|
143
226
|
result = await self.invoke(input_data)
|
|
144
227
|
final_chunk = {"output": result.get("output", ""), "type": "final"}
|
|
145
228
|
usage = self._extract_usage(result)
|
|
@@ -160,6 +243,12 @@ class LangChainRunner(BaseRunner):
|
|
|
160
243
|
final_chunk.setdefault("metadata", {})["last_usage"] = last_usage
|
|
161
244
|
yield final_chunk
|
|
162
245
|
|
|
246
|
+
def _should_stream_events(self, payload: dict[str, Any]) -> bool:
|
|
247
|
+
"""Use LangGraph events for LangChain create_agent message-state agents."""
|
|
248
|
+
return isinstance(payload.get("messages"), list) and hasattr(
|
|
249
|
+
self._agent, "astream_events"
|
|
250
|
+
)
|
|
251
|
+
|
|
163
252
|
def _resolve_request_path(self) -> str:
|
|
164
253
|
module = getattr(self, "_module", None)
|
|
165
254
|
if callable(getattr(module, "ksadk_prepare_input", None)):
|
|
@@ -168,7 +257,9 @@ class LangChainRunner(BaseRunner):
|
|
|
168
257
|
return "runnable_with_message_history"
|
|
169
258
|
return "replay"
|
|
170
259
|
|
|
171
|
-
def _prepare_with_standard_hook(
|
|
260
|
+
def _prepare_with_standard_hook(
|
|
261
|
+
self, input_data: Dict[str, Any], session_id: str
|
|
262
|
+
) -> dict[str, Any]:
|
|
172
263
|
module = getattr(self, "_module", None)
|
|
173
264
|
prepare_input = getattr(module, "ksadk_prepare_input", None)
|
|
174
265
|
if not callable(prepare_input):
|
|
@@ -195,7 +286,11 @@ class LangChainRunner(BaseRunner):
|
|
|
195
286
|
@staticmethod
|
|
196
287
|
def _ksadk_builtin_tool_context() -> dict[str, Any]:
|
|
197
288
|
try:
|
|
198
|
-
from ksadk.toolsets import
|
|
289
|
+
from ksadk.toolsets import (
|
|
290
|
+
builtin_tool_descriptors_for_runtime,
|
|
291
|
+
builtin_tools_mode,
|
|
292
|
+
builtin_tools_profile,
|
|
293
|
+
)
|
|
199
294
|
|
|
200
295
|
mode = builtin_tools_mode(default="off")
|
|
201
296
|
descriptors = builtin_tool_descriptors_for_runtime(mode=mode)
|
|
@@ -231,7 +326,11 @@ class LangChainRunner(BaseRunner):
|
|
|
231
326
|
def _ambient_context_text(input_data: Dict[str, Any]) -> str:
|
|
232
327
|
sections: list[str] = []
|
|
233
328
|
kb_context = input_data.get("kb_context") or {}
|
|
234
|
-
kb_text =
|
|
329
|
+
kb_text = (
|
|
330
|
+
str(kb_context.get("formatted_text") or "").strip()
|
|
331
|
+
if isinstance(kb_context, dict)
|
|
332
|
+
else ""
|
|
333
|
+
)
|
|
235
334
|
if kb_text:
|
|
236
335
|
sections.append(f"Knowledge base context:\n{kb_text}")
|
|
237
336
|
|
|
@@ -518,6 +617,119 @@ class LangChainRunner(BaseRunner):
|
|
|
518
617
|
return str(content)
|
|
519
618
|
return str(result)
|
|
520
619
|
|
|
620
|
+
@classmethod
|
|
621
|
+
def _extract_recognized_output(cls, result: Any) -> str:
|
|
622
|
+
if result is None:
|
|
623
|
+
return ""
|
|
624
|
+
if isinstance(result, str):
|
|
625
|
+
return result
|
|
626
|
+
|
|
627
|
+
if isinstance(result, dict):
|
|
628
|
+
for key in ("output", "text"):
|
|
629
|
+
if key not in result:
|
|
630
|
+
continue
|
|
631
|
+
value = result[key]
|
|
632
|
+
if isinstance(value, str):
|
|
633
|
+
return value
|
|
634
|
+
extracted = cls._extract_recognized_output(value)
|
|
635
|
+
if extracted:
|
|
636
|
+
return extracted
|
|
637
|
+
if value is not None and not isinstance(value, (dict, list, tuple)):
|
|
638
|
+
return str(value)
|
|
639
|
+
|
|
640
|
+
message_state = cls._extract_message_state(result)
|
|
641
|
+
return message_state[0] if message_state else ""
|
|
642
|
+
|
|
643
|
+
if isinstance(result, (list, tuple)):
|
|
644
|
+
for item in reversed(result):
|
|
645
|
+
extracted = cls._extract_recognized_output(item)
|
|
646
|
+
if extracted:
|
|
647
|
+
return extracted
|
|
648
|
+
return ""
|
|
649
|
+
|
|
650
|
+
command_update = getattr(result, "update", None)
|
|
651
|
+
if isinstance(command_update, dict):
|
|
652
|
+
return cls._extract_recognized_output(command_update)
|
|
653
|
+
|
|
654
|
+
content = cls._ai_message_content(result)
|
|
655
|
+
return content if content is not None else ""
|
|
656
|
+
|
|
657
|
+
@classmethod
|
|
658
|
+
def _extract_message_state(cls, chunk: Any) -> tuple[str, str] | None:
|
|
659
|
+
"""Extract the newest AI message from LangGraph values/updates snapshots."""
|
|
660
|
+
|
|
661
|
+
def visit(value: Any, path: tuple[str, ...]) -> tuple[str, str] | None:
|
|
662
|
+
if not isinstance(value, dict):
|
|
663
|
+
return None
|
|
664
|
+
|
|
665
|
+
messages = value.get("messages")
|
|
666
|
+
if isinstance(messages, list):
|
|
667
|
+
for index in range(len(messages) - 1, -1, -1):
|
|
668
|
+
message = messages[index]
|
|
669
|
+
content = cls._ai_message_content(message)
|
|
670
|
+
if content is None:
|
|
671
|
+
continue
|
|
672
|
+
message_id = (
|
|
673
|
+
message.get("id")
|
|
674
|
+
if isinstance(message, dict)
|
|
675
|
+
else getattr(message, "id", None)
|
|
676
|
+
)
|
|
677
|
+
fallback_key = "/".join((*path, "messages", str(index)))
|
|
678
|
+
return content, str(message_id or fallback_key)
|
|
679
|
+
|
|
680
|
+
for key, nested in value.items():
|
|
681
|
+
if key == "messages" or not isinstance(nested, dict):
|
|
682
|
+
continue
|
|
683
|
+
result = visit(nested, (*path, str(key)))
|
|
684
|
+
if result:
|
|
685
|
+
return result
|
|
686
|
+
return None
|
|
687
|
+
|
|
688
|
+
return visit(chunk, ())
|
|
689
|
+
|
|
690
|
+
@staticmethod
|
|
691
|
+
def _ai_message_content(message: Any) -> str | None:
|
|
692
|
+
if isinstance(message, dict):
|
|
693
|
+
role = str(message.get("role") or message.get("type") or "").lower()
|
|
694
|
+
if role not in {"ai", "assistant", "model", "aimessage", "aimessagechunk"}:
|
|
695
|
+
return None
|
|
696
|
+
content = message.get("content")
|
|
697
|
+
else:
|
|
698
|
+
role = str(getattr(message, "type", "") or "").lower()
|
|
699
|
+
class_name = type(message).__name__.lower()
|
|
700
|
+
if role not in {
|
|
701
|
+
"ai",
|
|
702
|
+
"assistant",
|
|
703
|
+
"model",
|
|
704
|
+
"aimessage",
|
|
705
|
+
"aimessagechunk",
|
|
706
|
+
} and not class_name.startswith("aimessage"):
|
|
707
|
+
return None
|
|
708
|
+
content = getattr(message, "content", None)
|
|
709
|
+
|
|
710
|
+
if isinstance(content, str):
|
|
711
|
+
return content
|
|
712
|
+
if not isinstance(content, list):
|
|
713
|
+
return None
|
|
714
|
+
|
|
715
|
+
parts: list[str] = []
|
|
716
|
+
for part in content:
|
|
717
|
+
if isinstance(part, str):
|
|
718
|
+
parts.append(part)
|
|
719
|
+
elif isinstance(part, dict):
|
|
720
|
+
text = part.get("text")
|
|
721
|
+
if isinstance(text, str):
|
|
722
|
+
parts.append(text)
|
|
723
|
+
return "".join(parts)
|
|
724
|
+
|
|
725
|
+
@staticmethod
|
|
726
|
+
def _snapshot_delta(content: str, previous: str) -> str:
|
|
727
|
+
if content.startswith(previous):
|
|
728
|
+
return content[len(previous) :]
|
|
729
|
+
if previous.startswith(content):
|
|
730
|
+
return ""
|
|
731
|
+
return content
|
|
732
|
+
|
|
521
733
|
def _extract_chunk(self, chunk: Any) -> tuple[Optional[str], Optional[str]]:
|
|
522
734
|
if isinstance(chunk, dict):
|
|
523
735
|
if "output" in chunk:
|
|
@@ -12,6 +12,7 @@ import base64
|
|
|
12
12
|
from pathlib import Path
|
|
13
13
|
|
|
14
14
|
from ksadk.runners.base_runner import BaseRunner
|
|
15
|
+
from ksadk.runners.usage_accumulator import accumulate_usage
|
|
15
16
|
from ksadk.sessions.continuity import LangGraphSessionAdapter
|
|
16
17
|
from ksadk.runners.utils import get_langfuse_callbacks, get_langfuse_metadata, load_agent_module
|
|
17
18
|
from langgraph.types import Command
|
|
@@ -259,6 +260,15 @@ class LangGraphRunner(BaseRunner):
|
|
|
259
260
|
return self._extract_usage(values)
|
|
260
261
|
return {}
|
|
261
262
|
|
|
263
|
+
@staticmethod
|
|
264
|
+
def _latest_checkpoint_config(config: dict[str, Any]) -> dict[str, Any]:
|
|
265
|
+
"""Select the newest state in a resumed thread, not the source checkpoint."""
|
|
266
|
+
latest_config = dict(config)
|
|
267
|
+
configurable = dict(latest_config.get("configurable") or {})
|
|
268
|
+
configurable.pop("checkpoint_id", None)
|
|
269
|
+
latest_config["configurable"] = configurable
|
|
270
|
+
return latest_config
|
|
271
|
+
|
|
262
272
|
@staticmethod
|
|
263
273
|
def _ambient_context_text(payload: Dict[str, Any]) -> str:
|
|
264
274
|
sections: list[str] = []
|
|
@@ -479,6 +489,51 @@ class LangGraphRunner(BaseRunner):
|
|
|
479
489
|
)
|
|
480
490
|
return self._agent.invoke(payload, **kwargs)
|
|
481
491
|
|
|
492
|
+
async def _invoke_from_stream_events(self, input_data: Dict[str, Any]) -> Dict[str, Any]:
|
|
493
|
+
chunks: list[dict[str, Any]] = []
|
|
494
|
+
accumulated_text = ""
|
|
495
|
+
final_chunk: dict[str, Any] | None = None
|
|
496
|
+
metadata: dict[str, Any] = {}
|
|
497
|
+
|
|
498
|
+
async for chunk in self.stream(input_data):
|
|
499
|
+
chunks.append(dict(chunk))
|
|
500
|
+
chunk_type = chunk.get("type")
|
|
501
|
+
if chunk_type == "text":
|
|
502
|
+
accumulated_text += str(chunk.get("delta") or "")
|
|
503
|
+
elif chunk_type == "final":
|
|
504
|
+
final_chunk = dict(chunk)
|
|
505
|
+
elif chunk_type == "checkpoint":
|
|
506
|
+
chunk_metadata = chunk.get("metadata")
|
|
507
|
+
if isinstance(chunk_metadata, Mapping):
|
|
508
|
+
metadata.update(dict(chunk_metadata))
|
|
509
|
+
elif chunk_type == "interrupt":
|
|
510
|
+
return {
|
|
511
|
+
"type": "interrupt",
|
|
512
|
+
"interrupt_info": chunk.get("interrupt_info"),
|
|
513
|
+
"session_id": chunk.get("session_id") or input_data.get("session_id"),
|
|
514
|
+
"output": (
|
|
515
|
+
chunk.get("interrupt_info", {}).get("message", "需要用户确认")
|
|
516
|
+
if isinstance(chunk.get("interrupt_info"), Mapping)
|
|
517
|
+
else "需要用户确认"
|
|
518
|
+
),
|
|
519
|
+
"raw": {"chunks": chunks},
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
if final_chunk:
|
|
523
|
+
output_text = str(final_chunk.get("output") or accumulated_text)
|
|
524
|
+
final_metadata = final_chunk.get("metadata")
|
|
525
|
+
if isinstance(final_metadata, Mapping):
|
|
526
|
+
metadata = {**dict(final_metadata), **metadata}
|
|
527
|
+
result: dict[str, Any] = {"output": output_text, "raw": {"chunks": chunks}}
|
|
528
|
+
usage = final_chunk.get("usage")
|
|
529
|
+
if isinstance(usage, Mapping) and usage:
|
|
530
|
+
result["usage"] = dict(usage)
|
|
531
|
+
if metadata:
|
|
532
|
+
result["metadata"] = metadata
|
|
533
|
+
return result
|
|
534
|
+
|
|
535
|
+
return {"output": accumulated_text, "raw": {"chunks": chunks}}
|
|
536
|
+
|
|
482
537
|
async def invoke(self, input_data: Dict[str, Any]) -> Dict[str, Any]:
|
|
483
538
|
"""调用 LangGraph 图
|
|
484
539
|
|
|
@@ -487,6 +542,10 @@ class LangGraphRunner(BaseRunner):
|
|
|
487
542
|
2. 原生格式: {"messages": [...]} 或自定义 State - 直接透传
|
|
488
543
|
"""
|
|
489
544
|
payload = dict(input_data)
|
|
545
|
+
force_graph_invoke = bool(payload.pop("_ksadk_force_graph_invoke", False))
|
|
546
|
+
if not force_graph_invoke and hasattr(self._agent, "astream_events"):
|
|
547
|
+
return await self._invoke_from_stream_events(payload)
|
|
548
|
+
|
|
490
549
|
session_id = payload.pop("session_id", None) or str(uuid.uuid4())[:8]
|
|
491
550
|
is_resume = payload.pop("resume", False)
|
|
492
551
|
is_checkpoint_resume = bool(payload.pop("checkpoint_resume", False))
|
|
@@ -618,14 +677,15 @@ class LangGraphRunner(BaseRunner):
|
|
|
618
677
|
else:
|
|
619
678
|
yield {"type": "graph_update", "output": update}
|
|
620
679
|
|
|
621
|
-
|
|
680
|
+
latest_config = self._latest_checkpoint_config(config)
|
|
681
|
+
state_usage = await self._latest_state_usage(latest_config)
|
|
622
682
|
state_output = ""
|
|
623
683
|
try:
|
|
624
684
|
state = None
|
|
625
685
|
if callable(getattr(self._agent, "aget_state", None)):
|
|
626
|
-
state = await self._agent.aget_state(
|
|
686
|
+
state = await self._agent.aget_state(latest_config)
|
|
627
687
|
elif callable(getattr(self._agent, "get_state", None)):
|
|
628
|
-
state = self._agent.get_state(
|
|
688
|
+
state = self._agent.get_state(latest_config)
|
|
629
689
|
values = getattr(state, "values", None)
|
|
630
690
|
if values is not None:
|
|
631
691
|
state_output = self._extract_output(values)
|
|
@@ -639,13 +699,14 @@ class LangGraphRunner(BaseRunner):
|
|
|
639
699
|
**({"resume_noop": True} if not emitted_update else {}),
|
|
640
700
|
}
|
|
641
701
|
|
|
642
|
-
metadata = await self._latest_checkpoint_metadata(
|
|
702
|
+
metadata = await self._latest_checkpoint_metadata(latest_config)
|
|
643
703
|
if metadata:
|
|
644
704
|
yield {"type": "checkpoint", "metadata": metadata}
|
|
645
705
|
|
|
646
706
|
async def stream(self, input_data: Dict[str, Any]) -> AsyncIterator[Dict[str, Any]]:
|
|
647
707
|
"""流式调用 LangGraph 图"""
|
|
648
708
|
payload = dict(input_data)
|
|
709
|
+
payload.pop("_ksadk_force_graph_invoke", None)
|
|
649
710
|
session_id = payload.pop("session_id", None) or str(uuid.uuid4())[:8]
|
|
650
711
|
history = payload.pop("history", [])
|
|
651
712
|
is_resume = payload.pop("resume", False)
|
|
@@ -689,6 +750,51 @@ class LangGraphRunner(BaseRunner):
|
|
|
689
750
|
emitted_non_text_event = False
|
|
690
751
|
final_output_text = ""
|
|
691
752
|
final_output_usage: dict[str, Any] = {}
|
|
753
|
+
final_output_last_usage: dict[str, Any] = {}
|
|
754
|
+
model_run_usages: dict[str, dict[str, Any]] = {}
|
|
755
|
+
model_run_order: list[str] = []
|
|
756
|
+
stream_usage_run_keys: set[str] = set()
|
|
757
|
+
latest_stream_usage: dict[str, Any] = {}
|
|
758
|
+
|
|
759
|
+
def model_run_key(
|
|
760
|
+
event: Mapping[str, Any],
|
|
761
|
+
*,
|
|
762
|
+
fallback_key: str | None = None,
|
|
763
|
+
) -> str:
|
|
764
|
+
raw_run_id = event.get("run_id")
|
|
765
|
+
return (
|
|
766
|
+
str(raw_run_id)
|
|
767
|
+
if raw_run_id
|
|
768
|
+
else fallback_key or f"model-event-{len(model_run_order)}"
|
|
769
|
+
)
|
|
770
|
+
|
|
771
|
+
def record_model_usage(
|
|
772
|
+
event: Mapping[str, Any],
|
|
773
|
+
usage: dict[str, Any],
|
|
774
|
+
*,
|
|
775
|
+
fallback_key: str | None = None,
|
|
776
|
+
) -> None:
|
|
777
|
+
if not usage:
|
|
778
|
+
return
|
|
779
|
+
run_key = model_run_key(event, fallback_key=fallback_key)
|
|
780
|
+
if run_key not in model_run_usages:
|
|
781
|
+
model_run_order.append(run_key)
|
|
782
|
+
model_run_usages[run_key] = dict(usage)
|
|
783
|
+
|
|
784
|
+
def accumulated_model_usage() -> dict[str, Any]:
|
|
785
|
+
if len(model_run_order) == 1:
|
|
786
|
+
return dict(model_run_usages.get(model_run_order[0]) or {})
|
|
787
|
+
usage: dict[str, Any] = {}
|
|
788
|
+
for run_key in model_run_order:
|
|
789
|
+
usage = accumulate_usage(usage, model_run_usages.get(run_key) or {})
|
|
790
|
+
return usage
|
|
791
|
+
|
|
792
|
+
def latest_model_usage() -> dict[str, Any]:
|
|
793
|
+
for run_key in reversed(model_run_order):
|
|
794
|
+
usage = model_run_usages.get(run_key)
|
|
795
|
+
if usage:
|
|
796
|
+
return dict(usage)
|
|
797
|
+
return {}
|
|
692
798
|
|
|
693
799
|
if is_checkpoint_resume and callable(getattr(self._agent, "astream", None)):
|
|
694
800
|
try:
|
|
@@ -731,6 +837,19 @@ class LangGraphRunner(BaseRunner):
|
|
|
731
837
|
chunk = event.get("data", {}).get("chunk")
|
|
732
838
|
if not chunk:
|
|
733
839
|
continue
|
|
840
|
+
chunk_usage = self._extract_usage(chunk)
|
|
841
|
+
if chunk_usage:
|
|
842
|
+
# Some LangChain providers attach cumulative usage to
|
|
843
|
+
# every stream chunk, and LangChain may then sum those
|
|
844
|
+
# cumulative snapshots into an inflated
|
|
845
|
+
# on_chat_model_end usage. For a concrete model run,
|
|
846
|
+
# keep the latest stream snapshot and ignore the later
|
|
847
|
+
# end usage for that same run_id.
|
|
848
|
+
latest_stream_usage = dict(chunk_usage)
|
|
849
|
+
if event.get("run_id"):
|
|
850
|
+
run_key = model_run_key(event)
|
|
851
|
+
stream_usage_run_keys.add(run_key)
|
|
852
|
+
record_model_usage(event, latest_stream_usage)
|
|
734
853
|
|
|
735
854
|
# 推理内容
|
|
736
855
|
reasoning = getattr(chunk, "reasoning_content", None)
|
|
@@ -760,6 +879,15 @@ class LangGraphRunner(BaseRunner):
|
|
|
760
879
|
accumulated_text += part.text
|
|
761
880
|
yield {"delta": part.text, "type": "text"}
|
|
762
881
|
|
|
882
|
+
elif event_kind == "on_chat_model_end":
|
|
883
|
+
data = event.get("data") or {}
|
|
884
|
+
output = data.get("output") if isinstance(data, Mapping) else None
|
|
885
|
+
usage = self._extract_usage(output) or self._extract_usage(data)
|
|
886
|
+
last_usage = self._extract_last_usage(output) or self._extract_last_usage(data)
|
|
887
|
+
run_key = model_run_key(event)
|
|
888
|
+
if run_key not in stream_usage_run_keys:
|
|
889
|
+
record_model_usage(event, last_usage or usage)
|
|
890
|
+
|
|
763
891
|
elif event_kind == "on_tool_start":
|
|
764
892
|
emitted_non_text_event = True
|
|
765
893
|
yield {
|
|
@@ -811,13 +939,17 @@ class LangGraphRunner(BaseRunner):
|
|
|
811
939
|
if not accumulated_text:
|
|
812
940
|
if final_output_text:
|
|
813
941
|
final_chunk = {"output": final_output_text, "type": "final"}
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
if
|
|
817
|
-
final_chunk
|
|
942
|
+
usage = accumulated_model_usage() or final_output_usage or latest_stream_usage
|
|
943
|
+
last_usage = latest_model_usage() or final_output_last_usage or latest_stream_usage or usage
|
|
944
|
+
if usage:
|
|
945
|
+
final_chunk["usage"] = usage
|
|
946
|
+
if last_usage:
|
|
947
|
+
final_chunk.setdefault("metadata", {})["last_usage"] = last_usage
|
|
818
948
|
yield final_chunk
|
|
819
949
|
elif not emitted_non_text_event:
|
|
820
|
-
result = await self.invoke(
|
|
950
|
+
result = await self.invoke(
|
|
951
|
+
{**invoke_payload, "_ksadk_force_graph_invoke": True}
|
|
952
|
+
)
|
|
821
953
|
final_chunk = {"output": result.get("output", ""), "type": "final"}
|
|
822
954
|
usage = self._extract_usage(result)
|
|
823
955
|
if usage:
|
|
@@ -832,11 +964,12 @@ class LangGraphRunner(BaseRunner):
|
|
|
832
964
|
return
|
|
833
965
|
else:
|
|
834
966
|
final_chunk = {"output": accumulated_text, "type": "final"}
|
|
835
|
-
|
|
967
|
+
state_usage = await self._latest_state_usage(config)
|
|
968
|
+
usage = accumulated_model_usage() or state_usage or final_output_usage or latest_stream_usage
|
|
836
969
|
if usage:
|
|
837
970
|
final_chunk["usage"] = usage
|
|
838
|
-
|
|
839
|
-
final_chunk.setdefault("metadata", {})["last_usage"] =
|
|
971
|
+
last_usage = latest_model_usage() or state_usage or final_output_last_usage or latest_stream_usage or usage
|
|
972
|
+
final_chunk.setdefault("metadata", {})["last_usage"] = last_usage
|
|
840
973
|
yield final_chunk
|
|
841
974
|
|
|
842
975
|
metadata = await self._latest_checkpoint_metadata(config)
|