agent-api-server 2.1.10__tar.gz → 2.1.11__tar.gz
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.
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/PKG-INFO +2 -2
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/acp_bridge/go/agent.go +19 -16
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/acp_bridge/go/main.go +40 -106
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/api/v1/thread.py +0 -150
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/pyproject.toml +1 -1
- agent_api_server-2.1.10/agent_api_server/.idea/.gitignore +0 -3
- agent_api_server-2.1.10/agent_api_server/.idea/agent_api_server.iml +0 -12
- agent_api_server-2.1.10/agent_api_server/.idea/inspectionProfiles/Project_Default.xml +0 -488
- agent_api_server-2.1.10/agent_api_server/.idea/inspectionProfiles/profiles_settings.xml +0 -6
- agent_api_server-2.1.10/agent_api_server/.idea/misc.xml +0 -4
- agent_api_server-2.1.10/agent_api_server/.idea/modules.xml +0 -8
- agent_api_server-2.1.10/agent_api_server/.idea/vcs.xml +0 -6
- agent_api_server-2.1.10/agent_api_server/.idea/workspace.xml +0 -50
- agent_api_server-2.1.10/agent_api_server/acp_bridge/go/agenthub-acp +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/README.md +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/__init__.py +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/a2a_bridge/__init__.py +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/a2a_bridge/agent_card_builder.py +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/a2a_bridge/call_a2a_client.py +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/a2a_bridge/langgraph_executor.py +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/a2a_bridge/redis_task_store.py +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/a2a_bridge/server.py +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/acp_bridge/go/Makefile +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/acp_bridge/go/go.mod +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/acp_bridge/go/go.sum +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/agent_registration.py +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/api/__init__.py +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/api/v1/__init__.py +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/api/v1/api.py +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/api/v1/config.py +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/api/v1/graph.py +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/api/v1/schema.py +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/app.py +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/cache/__init__.py +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/cache/redis_cache.py +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/callback_handler.py +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/client/css/styles.css +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/client/favicon.ico +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/client/index.html +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/client/js/app.js +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/client/js/index.umd.js +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/config_center/config_center.py +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/configs/__init__.py +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/configs/config.py +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/dynamic_llm/__init__.py +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/dynamic_llm/dynamic_llm.py +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/listener/__init__.py +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/listener/base.py +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/listener/nats_listener.py +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/listener/redis_listener.py +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/log/__init__.py +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/log/formatters.py +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/log/logging.json +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/mcp_convert/__init__.py +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/mcp_convert/mcp_convert.py +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/mcp_server.py +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/memory/__init__.py +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/memory/postgres.py +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/orchestrator/__init__.py +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/orchestrator/agent_registry.py +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/orchestrator/modes/__init__.py +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/orchestrator/modes/chain.py +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/orchestrator/modes/discussion.py +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/orchestrator/orchestrator.py +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/orchestrator/types.py +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/register/__init__.py +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/register/register.py +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/service.py +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/service_hub/service_hub.py +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/service_hub/service_hub_test.py +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/services/__init__.py +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/services/config_builder.py +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/services/stream_service.py +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/shared/__init__.py +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/shared/ase.py +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/shared/base_model.py +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/shared/common.py +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/shared/decode_token.py +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/shared/detect_message.py +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/shared/exceptions.py +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/shared/get_model_info.py +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/shared/graph_loader.py +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/shared/message.py +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/shared/model_config.py +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/shared/util_func.py +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/sso_service/__init__.py +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/sso_service/sdk/__init__.py +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/sso_service/sdk/client.py +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/sso_service/sdk/credential.py +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/sso_service/sdk/encoding.py +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/sso_service/sso_service.py +0 -0
- {agent_api_server-2.1.10 → agent_api_server-2.1.11}/agent_api_server/startup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: agent-api-server
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.11
|
|
4
4
|
Summary: A Langgraph agent API server that implements Langgraph agent's web capabilities and can interact with chatbot
|
|
5
5
|
Author: zijie.zhang@advantech.com.cn
|
|
6
6
|
Requires-Python: >=3.10,<3.13
|
|
@@ -39,6 +39,12 @@ type SessionState struct {
|
|
|
39
39
|
UpdatedAt string `json:"updated_at,omitempty"`
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
// pendingToolCall holds buffered tool call info until the tool result is available.
|
|
43
|
+
type pendingToolCall struct {
|
|
44
|
+
name string
|
|
45
|
+
args interface{}
|
|
46
|
+
}
|
|
47
|
+
|
|
42
48
|
// AgentHubACPAgent implements the ACP protocol over stdio, proxying to an HTTP backend.
|
|
43
49
|
type AgentHubACPAgent struct {
|
|
44
50
|
serviceURL string
|
|
@@ -54,7 +60,7 @@ type AgentHubACPAgent struct {
|
|
|
54
60
|
mu sync.Mutex
|
|
55
61
|
httpClient *http.Client
|
|
56
62
|
sessions map[string]*SessionState
|
|
57
|
-
pendingToolCalls map[string]map[string]
|
|
63
|
+
pendingToolCalls map[string]map[string]*pendingToolCall // sessionID -> tcID -> info
|
|
58
64
|
streamedNodes map[string]map[string]bool // sessionID -> set of nodes
|
|
59
65
|
}
|
|
60
66
|
|
|
@@ -77,7 +83,7 @@ func NewAgentHubACPAgent(
|
|
|
77
83
|
extraInputs: extraInputs,
|
|
78
84
|
streamMode: streamMode,
|
|
79
85
|
sessions: make(map[string]*SessionState),
|
|
80
|
-
pendingToolCalls: make(map[string]map[string]
|
|
86
|
+
pendingToolCalls: make(map[string]map[string]*pendingToolCall),
|
|
81
87
|
streamedNodes: make(map[string]map[string]bool),
|
|
82
88
|
httpClient: &http.Client{
|
|
83
89
|
Timeout: 300 * time.Second,
|
|
@@ -348,24 +354,22 @@ func (a *AgentHubACPAgent) processSSEEvent(sessionID, raw string) {
|
|
|
348
354
|
a.mu.Lock()
|
|
349
355
|
pending := a.pendingToolCalls[sessionID]
|
|
350
356
|
if pending == nil {
|
|
351
|
-
pending = make(map[string]
|
|
357
|
+
pending = make(map[string]*pendingToolCall)
|
|
352
358
|
a.pendingToolCalls[sessionID] = pending
|
|
353
359
|
}
|
|
354
360
|
a.mu.Unlock()
|
|
355
361
|
for _, tc := range toolCalls {
|
|
356
|
-
tcID
|
|
357
|
-
|
|
358
|
-
tcID = uuid.New().String()
|
|
359
|
-
}
|
|
362
|
+
// Always use UUID for tcID to avoid invalid IDs from LangChain
|
|
363
|
+
tcID := uuid.New().String()
|
|
360
364
|
tcName, _ := tc["name"].(string)
|
|
361
365
|
if tcName == "" {
|
|
362
366
|
tcName = "unknown"
|
|
363
367
|
}
|
|
364
368
|
tcArgs := tc["args"]
|
|
369
|
+
// Buffer tool call info; emit complete tool_call when tools_message arrives
|
|
365
370
|
a.mu.Lock()
|
|
366
|
-
pending[tcID] = tcName
|
|
371
|
+
pending[tcID] = &pendingToolCall{name: tcName, args: tcArgs}
|
|
367
372
|
a.mu.Unlock()
|
|
368
|
-
a.sendStartToolCall(sessionID, tcID, tcName, tcArgs, nil, "")
|
|
369
373
|
}
|
|
370
374
|
} else if text != "" {
|
|
371
375
|
a.mu.Lock()
|
|
@@ -390,12 +394,13 @@ func (a *AgentHubACPAgent) processSSEEvent(sessionID, raw string) {
|
|
|
390
394
|
a.mu.Unlock()
|
|
391
395
|
|
|
392
396
|
if len(pending) > 0 {
|
|
393
|
-
for tcID,
|
|
397
|
+
for tcID, info := range pending {
|
|
394
398
|
output := text
|
|
395
399
|
if output == "" {
|
|
396
|
-
output =
|
|
400
|
+
output = info.name + " completed"
|
|
397
401
|
}
|
|
398
|
-
|
|
402
|
+
// Emit single complete tool_call with rawInput + rawOutput
|
|
403
|
+
a.sendStartToolCall(sessionID, tcID, info.name, info.args, output, "completed")
|
|
399
404
|
}
|
|
400
405
|
a.mu.Lock()
|
|
401
406
|
delete(a.pendingToolCalls, sessionID)
|
|
@@ -412,10 +417,8 @@ func (a *AgentHubACPAgent) processSSEEvent(sessionID, raw string) {
|
|
|
412
417
|
}
|
|
413
418
|
}
|
|
414
419
|
for _, tc := range toolCalls {
|
|
415
|
-
tcID
|
|
416
|
-
|
|
417
|
-
tcID = uuid.New().String()
|
|
418
|
-
}
|
|
420
|
+
// Always use UUID for tcID to avoid invalid IDs from LangChain
|
|
421
|
+
tcID := uuid.New().String()
|
|
419
422
|
tcName, _ := tc["name"].(string)
|
|
420
423
|
if tcName == "" {
|
|
421
424
|
tcName = "unknown"
|
|
@@ -317,129 +317,48 @@ func (a *AgentHubACPAgent) loadSessionHistory(sessionID, cwd string) {
|
|
|
317
317
|
|
|
318
318
|
var data struct {
|
|
319
319
|
Messages []map[string]interface{} `json:"messages"`
|
|
320
|
-
NodeExecutions []map[string]interface{} `json:"node_executions"`
|
|
321
320
|
}
|
|
322
321
|
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
|
|
323
322
|
log.Printf("Failed to decode session history: %v", err)
|
|
324
323
|
return
|
|
325
324
|
}
|
|
326
325
|
|
|
327
|
-
|
|
328
|
-
a.replayNodeExecutions(sessionID, data.NodeExecutions)
|
|
329
|
-
} else {
|
|
330
|
-
a.replayMessages(sessionID, data.Messages)
|
|
331
|
-
}
|
|
326
|
+
a.replayMessages(sessionID, data.Messages)
|
|
332
327
|
|
|
333
328
|
log.Printf("Loaded session %s with %d messages", sessionID, len(data.Messages))
|
|
334
329
|
}
|
|
335
330
|
|
|
336
|
-
// replayNodeExecutions replays session history using node_executions data.
|
|
337
|
-
// Each nodeExecution represents one turn (user question + AI response).
|
|
338
|
-
// We process messages in order within each turn to maintain proper conversation flow.
|
|
339
|
-
func (a *AgentHubACPAgent) replayNodeExecutions(sessionID string, nodeExecutions []map[string]interface{}) {
|
|
340
|
-
shownContents := make(map[string]bool)
|
|
341
|
-
|
|
342
|
-
for _, execInfo := range nodeExecutions {
|
|
343
|
-
nodesRaw, _ := execInfo["nodes"].([]interface{})
|
|
344
|
-
execMessagesRaw, _ := execInfo["messages"].([]interface{})
|
|
345
331
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
332
|
+
// replayMessages replays session history using simple messages list.
|
|
333
|
+
func (a *AgentHubACPAgent) replayMessages(sessionID string, messages []map[string]interface{}) {
|
|
334
|
+
// Pre-scan: collect tool results by name for including in tool_call events
|
|
335
|
+
toolResultsByName := make(map[string][]string)
|
|
336
|
+
for _, msg := range messages {
|
|
337
|
+
if msgType, _ := msg["type"].(string); msgType == "tool" {
|
|
338
|
+
toolName, _ := msg["name"].(string)
|
|
339
|
+
if toolName == "" {
|
|
340
|
+
toolName = "unknown"
|
|
350
341
|
}
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
var execMessages []map[string]interface{}
|
|
354
|
-
for _, m := range execMessagesRaw {
|
|
355
|
-
if mm, ok := m.(map[string]interface{}); ok {
|
|
356
|
-
execMessages = append(execMessages, mm)
|
|
342
|
+
if c, _ := msg["content"].(string); c != "" {
|
|
343
|
+
toolResultsByName[toolName] = append(toolResultsByName[toolName], c)
|
|
357
344
|
}
|
|
358
345
|
}
|
|
359
|
-
|
|
360
|
-
// Process messages in order: first emit human message, then AI/tool responses
|
|
361
|
-
humanEmitted := false
|
|
362
|
-
for _, msg := range execMessages {
|
|
363
|
-
msgType, _ := msg["type"].(string)
|
|
364
|
-
msgContent, _ := msg["content"].(string)
|
|
365
|
-
msgName, _ := msg["name"].(string)
|
|
366
|
-
|
|
367
|
-
if msgType == "human" {
|
|
368
|
-
// Emit user message
|
|
369
|
-
if msgContent != "" {
|
|
370
|
-
a.sendUserMessage(sessionID, msgContent)
|
|
371
|
-
humanEmitted = true
|
|
372
|
-
}
|
|
373
|
-
continue
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
// For AI and tool messages, check if this is a node output
|
|
377
|
-
if nodeSet := make(map[string]bool); len(nodes) > 0 {
|
|
378
|
-
for _, n := range nodes {
|
|
379
|
-
nodeSet[n] = true
|
|
380
|
-
}
|
|
381
|
-
if nodeSet[msgName] && msgContent != "" {
|
|
382
|
-
// This is a node output - emit as tool call + agent message
|
|
383
|
-
if !shownContents[msgContent] {
|
|
384
|
-
a.sendStartToolCall(sessionID, uuid.New().String(), msgName, nil, nil, "completed")
|
|
385
|
-
a.sendAgentMessage(sessionID, fmt.Sprintf("[%s]\n%s", msgName, msgContent))
|
|
386
|
-
shownContents[msgContent] = true
|
|
387
|
-
}
|
|
388
|
-
continue
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
// Handle AI messages with tool_calls
|
|
393
|
-
if msgType == "ai" {
|
|
394
|
-
if toolCallsRaw, ok := msg["tool_calls"].([]interface{}); ok {
|
|
395
|
-
for _, tc := range toolCallsRaw {
|
|
396
|
-
if tcm, ok := tc.(map[string]interface{}); ok {
|
|
397
|
-
tcName, _ := tcm["name"].(string)
|
|
398
|
-
if tcName == "" {
|
|
399
|
-
tcName = "unknown"
|
|
400
|
-
}
|
|
401
|
-
tcArgs := tcm["args"]
|
|
402
|
-
a.sendStartToolCall(sessionID, uuid.New().String(), tcName, tcArgs, nil, "completed")
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
if msgContent != "" && !shownContents[msgContent] {
|
|
407
|
-
a.sendAgentMessage(sessionID, msgContent)
|
|
408
|
-
shownContents[msgContent] = true
|
|
409
|
-
}
|
|
410
|
-
} else if msgType == "tool" {
|
|
411
|
-
toolName, _ := msg["name"].(string)
|
|
412
|
-
if toolName == "" {
|
|
413
|
-
toolName = "unknown"
|
|
414
|
-
}
|
|
415
|
-
if msgContent != "" && !shownContents[msgContent] {
|
|
416
|
-
a.sendAgentMessage(sessionID, fmt.Sprintf("[tool:%s] %s", toolName, msgContent))
|
|
417
|
-
shownContents[msgContent] = true
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
// If no human message was emitted but we have messages, something is wrong
|
|
423
|
-
// (this shouldn't happen with proper turn detection)
|
|
424
|
-
if !humanEmitted && len(execMessages) > 0 {
|
|
425
|
-
log.Printf("Warning: turn with no human message emitted, nodes=%v", nodes)
|
|
426
|
-
}
|
|
427
346
|
}
|
|
428
|
-
|
|
347
|
+
toolResultIdx := make(map[string]int)
|
|
429
348
|
|
|
430
|
-
// replayMessages replays session history using simple messages list.
|
|
431
|
-
func (a *AgentHubACPAgent) replayMessages(sessionID string, messages []map[string]interface{}) {
|
|
432
349
|
for _, msg := range messages {
|
|
433
350
|
msgType, _ := msg["type"].(string)
|
|
434
351
|
msgContent, _ := msg["content"].(string)
|
|
352
|
+
msgName, _ := msg["name"].(string)
|
|
435
353
|
|
|
436
354
|
switch msgType {
|
|
437
355
|
case "human":
|
|
438
356
|
a.sendUserMessage(sessionID, msgContent)
|
|
439
357
|
|
|
440
358
|
case "ai":
|
|
441
|
-
//
|
|
442
|
-
if toolCallsRaw, ok := msg["tool_calls"].([]interface{}); ok {
|
|
359
|
+
// Check if this AI message has tool_calls (ReAct Agent pattern)
|
|
360
|
+
if toolCallsRaw, ok := msg["tool_calls"].([]interface{}); ok && len(toolCallsRaw) > 0 {
|
|
361
|
+
// ReAct Agent pattern: AI message with tool_calls
|
|
443
362
|
for _, tc := range toolCallsRaw {
|
|
444
363
|
if tcm, ok := tc.(map[string]interface{}); ok {
|
|
445
364
|
tcName, _ := tcm["name"].(string)
|
|
@@ -447,20 +366,35 @@ func (a *AgentHubACPAgent) replayMessages(sessionID string, messages []map[strin
|
|
|
447
366
|
tcName = "unknown"
|
|
448
367
|
}
|
|
449
368
|
tcArgs := tcm["args"]
|
|
450
|
-
|
|
369
|
+
tcID := uuid.New().String()
|
|
370
|
+
// Include tool output in the tool_call event
|
|
371
|
+
var rawOutput interface{}
|
|
372
|
+
if results, ok := toolResultsByName[tcName]; ok {
|
|
373
|
+
idx := toolResultIdx[tcName]
|
|
374
|
+
if idx < len(results) {
|
|
375
|
+
rawOutput = results[idx]
|
|
376
|
+
toolResultIdx[tcName] = idx + 1
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
a.sendStartToolCall(sessionID, tcID, tcName, tcArgs, rawOutput, "completed")
|
|
451
380
|
}
|
|
452
381
|
}
|
|
453
|
-
|
|
454
|
-
|
|
382
|
+
if msgContent != "" {
|
|
383
|
+
a.sendAgentMessage(sessionID, msgContent)
|
|
384
|
+
}
|
|
385
|
+
} else if msgName != "" && msgContent != "" {
|
|
386
|
+
// Node pattern: AI message without tool_calls, but has name (node name)
|
|
387
|
+
tcID := uuid.New().String()
|
|
388
|
+
a.sendStartToolCall(sessionID, tcID, msgName, nil, msgContent, "completed")
|
|
389
|
+
a.sendAgentMessage(sessionID, fmt.Sprintf("[%s]\n%s", msgName, msgContent))
|
|
390
|
+
} else if msgContent != "" {
|
|
391
|
+
// Regular AI message
|
|
455
392
|
a.sendAgentMessage(sessionID, msgContent)
|
|
456
393
|
}
|
|
457
394
|
|
|
458
395
|
case "tool":
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
toolName = "unknown"
|
|
462
|
-
}
|
|
463
|
-
a.sendAgentMessage(sessionID, fmt.Sprintf("[tool:%s] %s", toolName, msgContent))
|
|
396
|
+
// Tool results are already included in the preceding AI message's
|
|
397
|
+
// tool_call rawOutput, so skip standalone tool messages.
|
|
464
398
|
}
|
|
465
399
|
}
|
|
466
400
|
}
|
|
@@ -626,7 +626,6 @@ async def get_thread_history(
|
|
|
626
626
|
thread_id: str,
|
|
627
627
|
limit: int = Query(50, ge=1, le=200, description="Max messages to return"),
|
|
628
628
|
user_input_field: Optional[str] = Query(None, description="User input field name"),
|
|
629
|
-
include_node_history: bool = Query(False, description="Include node execution history"),
|
|
630
629
|
) -> Dict[str, Any]:
|
|
631
630
|
try:
|
|
632
631
|
from agent_api_server.memory.postgres import AsyncPostgresCheckpointer
|
|
@@ -706,155 +705,6 @@ async def get_thread_history(
|
|
|
706
705
|
"next_config": state.next if hasattr(state, "next") else None,
|
|
707
706
|
}
|
|
708
707
|
|
|
709
|
-
if include_node_history:
|
|
710
|
-
node_executions = []
|
|
711
|
-
snapshots = []
|
|
712
|
-
async for snapshot in graph_instance.aget_state_history(config):
|
|
713
|
-
snapshots.append(snapshot)
|
|
714
|
-
|
|
715
|
-
snapshots.reverse()
|
|
716
|
-
|
|
717
|
-
from langchain_core.messages import HumanMessage as _HumanMessage
|
|
718
|
-
|
|
719
|
-
# Detect turns based on state history snapshots.
|
|
720
|
-
# Each snapshot represents a checkpoint; we detect new user
|
|
721
|
-
# inputs by comparing either string values or HumanMessage
|
|
722
|
-
# counts (when input_field points to a list like "messages").
|
|
723
|
-
turn_boundaries = [] # list of (msg_index_of_human_msg, user_text)
|
|
724
|
-
|
|
725
|
-
prev_human_count = 0
|
|
726
|
-
prev_user_input_str = None
|
|
727
|
-
|
|
728
|
-
for i, snapshot in enumerate(snapshots):
|
|
729
|
-
msgs = snapshot.values.get("messages", []) if snapshot.values else []
|
|
730
|
-
if not (snapshot.values and input_field):
|
|
731
|
-
continue
|
|
732
|
-
|
|
733
|
-
val = snapshot.values.get(input_field)
|
|
734
|
-
current_user_input = None
|
|
735
|
-
|
|
736
|
-
if val and isinstance(val, str):
|
|
737
|
-
current_user_input = val.strip()
|
|
738
|
-
if (current_user_input is not None and
|
|
739
|
-
current_user_input != prev_user_input_str):
|
|
740
|
-
# Find the last HumanMessage in current msgs
|
|
741
|
-
human_idx = None
|
|
742
|
-
for j in range(len(msgs) - 1, -1, -1):
|
|
743
|
-
if isinstance(msgs[j], _HumanMessage):
|
|
744
|
-
human_idx = j
|
|
745
|
-
break
|
|
746
|
-
if human_idx is not None:
|
|
747
|
-
turn_boundaries.append((human_idx, current_user_input))
|
|
748
|
-
prev_user_input_str = current_user_input
|
|
749
|
-
|
|
750
|
-
elif isinstance(val, list):
|
|
751
|
-
# input_field points to the messages list itself.
|
|
752
|
-
# Find new HumanMessages by comparing count with previous snapshot.
|
|
753
|
-
def _is_human_message(m):
|
|
754
|
-
"""Check if a message is a HumanMessage (supports both object and dict formats)."""
|
|
755
|
-
if isinstance(m, _HumanMessage):
|
|
756
|
-
return True
|
|
757
|
-
if isinstance(m, dict) and m.get("type") == "human":
|
|
758
|
-
return True
|
|
759
|
-
if hasattr(m, "type") and getattr(m, "type", None) == "human":
|
|
760
|
-
return True
|
|
761
|
-
return False
|
|
762
|
-
|
|
763
|
-
def _get_message_content(m):
|
|
764
|
-
"""Get content from a message (supports both object and dict formats)."""
|
|
765
|
-
if isinstance(m, dict):
|
|
766
|
-
return m.get("content", "")
|
|
767
|
-
return m.content if hasattr(m, "content") else str(m)
|
|
768
|
-
|
|
769
|
-
human_indices_in_msgs = [
|
|
770
|
-
j for j, m in enumerate(val)
|
|
771
|
-
if _is_human_message(m)
|
|
772
|
-
]
|
|
773
|
-
human_count = len(human_indices_in_msgs)
|
|
774
|
-
if human_count > prev_human_count:
|
|
775
|
-
# New HumanMessage(s) detected.
|
|
776
|
-
# Use the ACTUAL index in the messages list,
|
|
777
|
-
# not human_count - 1 (which is wrong when AI/tool
|
|
778
|
-
# messages are interspersed).
|
|
779
|
-
for k in range(prev_human_count, human_count):
|
|
780
|
-
actual_idx = human_indices_in_msgs[k]
|
|
781
|
-
new_human = val[actual_idx]
|
|
782
|
-
content_str = _get_message_content(new_human)
|
|
783
|
-
if isinstance(content_str, str):
|
|
784
|
-
content_str = content_str.strip()
|
|
785
|
-
else:
|
|
786
|
-
content_str = str(content_str)
|
|
787
|
-
turn_boundaries.append((actual_idx, content_str))
|
|
788
|
-
prev_human_count = human_count
|
|
789
|
-
|
|
790
|
-
# Deduplicate boundaries by msg index (keep first occurrence)
|
|
791
|
-
seen_indices = set()
|
|
792
|
-
unique_boundaries = []
|
|
793
|
-
for idx, text in turn_boundaries:
|
|
794
|
-
if idx not in seen_indices:
|
|
795
|
-
seen_indices.add(idx)
|
|
796
|
-
unique_boundaries.append((idx, text))
|
|
797
|
-
turn_boundaries = unique_boundaries
|
|
798
|
-
|
|
799
|
-
# Build node_executions from turn boundaries
|
|
800
|
-
final_snapshot = snapshots[-1] if snapshots else None
|
|
801
|
-
if final_snapshot and final_snapshot.values:
|
|
802
|
-
final_msgs = final_snapshot.values.get("messages", [])
|
|
803
|
-
|
|
804
|
-
# Collect all node names from all snapshots for each turn range
|
|
805
|
-
for turn_idx, (human_msg_idx, user_text) in enumerate(turn_boundaries):
|
|
806
|
-
# Determine message range for this turn
|
|
807
|
-
start_idx = human_msg_idx
|
|
808
|
-
if turn_idx + 1 < len(turn_boundaries):
|
|
809
|
-
end_idx = turn_boundaries[turn_idx + 1][0]
|
|
810
|
-
else:
|
|
811
|
-
end_idx = len(final_msgs)
|
|
812
|
-
|
|
813
|
-
# Collect node names that executed in this turn's snapshots
|
|
814
|
-
turn_nodes = []
|
|
815
|
-
for snapshot in snapshots:
|
|
816
|
-
s_msgs = snapshot.values.get("messages", []) if snapshot.values else []
|
|
817
|
-
s_len = len(s_msgs)
|
|
818
|
-
# This snapshot belongs to this turn if it overlaps
|
|
819
|
-
# with the turn's message range
|
|
820
|
-
if s_len > start_idx and (turn_idx + 1 >= len(turn_boundaries) or s_len <= end_idx + 1):
|
|
821
|
-
node_names = [task.name for task in snapshot.tasks if task.name != "__start__"] if snapshot.tasks else []
|
|
822
|
-
for n in node_names:
|
|
823
|
-
if n not in turn_nodes:
|
|
824
|
-
turn_nodes.append(n)
|
|
825
|
-
|
|
826
|
-
# Build turn messages
|
|
827
|
-
turn_messages = [{
|
|
828
|
-
"type": "human",
|
|
829
|
-
"content": user_text,
|
|
830
|
-
"name": None,
|
|
831
|
-
"tool_calls": [],
|
|
832
|
-
}]
|
|
833
|
-
|
|
834
|
-
# Add non-human messages in range (skip the human message at start_idx
|
|
835
|
-
# since we already added it above)
|
|
836
|
-
for msg in final_msgs[start_idx + 1:end_idx]:
|
|
837
|
-
turn_messages.append({
|
|
838
|
-
"type": getattr(msg, "type", "unknown"),
|
|
839
|
-
"content": msg.content if hasattr(msg, "content") else str(msg),
|
|
840
|
-
"name": getattr(msg, "name", None),
|
|
841
|
-
"tool_calls": [
|
|
842
|
-
{"name": tc.get("name", ""), "args": tc.get("args", {})}
|
|
843
|
-
for tc in (getattr(msg, "tool_calls", []) or [])
|
|
844
|
-
] if hasattr(msg, "tool_calls") else [],
|
|
845
|
-
})
|
|
846
|
-
|
|
847
|
-
node_executions.append({
|
|
848
|
-
"step": turn_idx,
|
|
849
|
-
"nodes": turn_nodes,
|
|
850
|
-
"messages": turn_messages,
|
|
851
|
-
})
|
|
852
|
-
elif not turn_boundaries:
|
|
853
|
-
# Fallback: no turn boundaries detected, use simple messages
|
|
854
|
-
pass
|
|
855
|
-
|
|
856
|
-
result["node_executions"] = node_executions
|
|
857
|
-
|
|
858
708
|
return result
|
|
859
709
|
|
|
860
710
|
except HTTPException:
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
-
<module type="PYTHON_MODULE" version="4">
|
|
3
|
-
<component name="NewModuleRootManager">
|
|
4
|
-
<content url="file://$MODULE_DIR$" />
|
|
5
|
-
<orderEntry type="jdk" jdkName="Poetry (agentbuilder-tools)" jdkType="Python SDK" />
|
|
6
|
-
<orderEntry type="sourceFolder" forTests="false" />
|
|
7
|
-
</component>
|
|
8
|
-
<component name="PyDocumentationSettings">
|
|
9
|
-
<option name="format" value="PLAIN" />
|
|
10
|
-
<option name="myDocStringFormat" value="Plain" />
|
|
11
|
-
</component>
|
|
12
|
-
</module>
|