agentvis 0.1.0__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.
- agentvis-0.1.0/PKG-INFO +103 -0
- agentvis-0.1.0/README.md +90 -0
- agentvis-0.1.0/agentvis/__init__.py +10 -0
- agentvis-0.1.0/agentvis/core/__init__.py +1 -0
- agentvis-0.1.0/agentvis/core/connection_creation_strategy/__init__.py +3 -0
- agentvis-0.1.0/agentvis/core/connection_creation_strategy/base.py +14 -0
- agentvis-0.1.0/agentvis/core/connection_creation_strategy/context.py +15 -0
- agentvis-0.1.0/agentvis/core/connection_creation_strategy/helper.py +18 -0
- agentvis-0.1.0/agentvis/core/connection_creation_strategy/strategy_tool_to_tool.py +55 -0
- agentvis-0.1.0/agentvis/core/export/__init__.py +1 -0
- agentvis-0.1.0/agentvis/core/export/base.py +10 -0
- agentvis-0.1.0/agentvis/core/export/json.py +13 -0
- agentvis-0.1.0/agentvis/core/export/link.py +25 -0
- agentvis-0.1.0/agentvis/core/export/main.py +15 -0
- agentvis-0.1.0/agentvis/core/main.py +51 -0
- agentvis-0.1.0/agentvis/core/models.py +55 -0
- agentvis-0.1.0/agentvis/core/retriever_strategy/__init__.py +2 -0
- agentvis-0.1.0/agentvis/core/retriever_strategy/base.py +15 -0
- agentvis-0.1.0/agentvis/core/retriever_strategy/bm25.py +24 -0
- agentvis-0.1.0/agentvis/core/retriever_strategy/context.py +15 -0
- agentvis-0.1.0/agentvis/core/retriever_strategy/models.py +5 -0
- agentvis-0.1.0/agentvis/framework/__init__.py +0 -0
- agentvis-0.1.0/agentvis/framework/base.py +10 -0
- agentvis-0.1.0/agentvis/framework/langchain/__init__.py +1 -0
- agentvis-0.1.0/agentvis/framework/langchain/main.py +36 -0
- agentvis-0.1.0/agentvis.egg-info/PKG-INFO +103 -0
- agentvis-0.1.0/agentvis.egg-info/SOURCES.txt +30 -0
- agentvis-0.1.0/agentvis.egg-info/dependency_links.txt +1 -0
- agentvis-0.1.0/agentvis.egg-info/requires.txt +5 -0
- agentvis-0.1.0/agentvis.egg-info/top_level.txt +1 -0
- agentvis-0.1.0/pyproject.toml +27 -0
- agentvis-0.1.0/setup.cfg +4 -0
agentvis-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agentvis
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Framework-agnostic reasoning trace visualization tool for AI agents.
|
|
5
|
+
Author: Nitesh Kumar
|
|
6
|
+
License: Apache License 2.0
|
|
7
|
+
Requires-Python: >=3.9
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: bm25s==0.3.0
|
|
10
|
+
Requires-Dist: pydantic>=2.0.0
|
|
11
|
+
Provides-Extra: langchain
|
|
12
|
+
Requires-Dist: langchain>=0.1.0; extra == "langchain"
|
|
13
|
+
|
|
14
|
+
# agentvis
|
|
15
|
+
|
|
16
|
+
<p align="center">
|
|
17
|
+
<picture align="center">
|
|
18
|
+
<img align="center" alt="agentvis logo" src="https://raw.githubusercontent.com/kumarniteshpersonal-beep/agentvis/968caea6e04577583d7d16f7015b9925362d5f53/packages/ui/public/agentvis_logo.svg" width="560">
|
|
19
|
+
</picture>
|
|
20
|
+
</p>
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
*agentvis* visualizes an agent’s reasoning trace, giving you clear insight into *why* the agent chose a particular path or triggered a specific tool call.
|
|
24
|
+
|
|
25
|
+
By exposing the influence flow behind each decision, it transforms opaque agent behavior into something understandable and actionable — so you can confidently refine and improve your prompts, which is often the hardest part of building reliable agents. 🧠✨
|
|
26
|
+
|
|
27
|
+
## *agentvis* reasoning visualization example
|
|
28
|
+
|
|
29
|
+

|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
Install core:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pip install agentvis
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
With LangChain support:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install agentvis[langchain]
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
## How to generate an agent reasoning graph
|
|
47
|
+
|
|
48
|
+
### 1. Define your LangChain / LangGraph agent
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from langchain_tavily import TavilySearch
|
|
52
|
+
from langchain_google_genai import ChatGoogleGenerativeAI
|
|
53
|
+
from langchain.agents import create_agent
|
|
54
|
+
from langchain.messages import HumanMessage
|
|
55
|
+
import os
|
|
56
|
+
|
|
57
|
+
os.environ["TAVILY_API_KEY"] = "<your-tavily-api-key>"
|
|
58
|
+
|
|
59
|
+
tavily_search = TavilySearch(
|
|
60
|
+
max_results=5,
|
|
61
|
+
search_depth="basic",
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
llm = ChatGoogleGenerativeAI(
|
|
65
|
+
model="gemini-2.5-flash-lite",
|
|
66
|
+
temperature=0.7,
|
|
67
|
+
google_api_key="<your-google-api-key>",
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
agent = create_agent(
|
|
71
|
+
llm,
|
|
72
|
+
[tavily_search],
|
|
73
|
+
system_prompt=(
|
|
74
|
+
"You are a helpful web search agent. "
|
|
75
|
+
"Use the Tavily tool when you need fresh information from the web."
|
|
76
|
+
),
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
result = agent.invoke(
|
|
80
|
+
{"messages": [HumanMessage("Top warm countries and weather of each.")]}
|
|
81
|
+
)
|
|
82
|
+
messages = result["messages"]
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### 2. Build the reasoning graph with *agentvis* and get a shareable link in exchange of messages produced by agent
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
from agentvis.framework.langchain import LangChainAdapter
|
|
89
|
+
from agentvis.core.export import ExportFactory
|
|
90
|
+
|
|
91
|
+
# Convert LLM messages into an AgentGraph
|
|
92
|
+
graph = LangChainAdapter().build_agent_graph(messages)
|
|
93
|
+
|
|
94
|
+
# Export as a short link you can open in the UI
|
|
95
|
+
link = ExportFactory.export_graph(graph=graph, export_strategy="link") # or "json"
|
|
96
|
+
print(link)
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
> Open the printed `link` in your browser to inspect the full reasoning trace of the `websearch` agent.
|
|
100
|
+
|
|
101
|
+
## License
|
|
102
|
+
|
|
103
|
+
This project is licensed under the Apache License 2.0.
|
agentvis-0.1.0/README.md
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# agentvis
|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
<picture align="center">
|
|
5
|
+
<img align="center" alt="agentvis logo" src="https://raw.githubusercontent.com/kumarniteshpersonal-beep/agentvis/968caea6e04577583d7d16f7015b9925362d5f53/packages/ui/public/agentvis_logo.svg" width="560">
|
|
6
|
+
</picture>
|
|
7
|
+
</p>
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
*agentvis* visualizes an agent’s reasoning trace, giving you clear insight into *why* the agent chose a particular path or triggered a specific tool call.
|
|
11
|
+
|
|
12
|
+
By exposing the influence flow behind each decision, it transforms opaque agent behavior into something understandable and actionable — so you can confidently refine and improve your prompts, which is often the hardest part of building reliable agents. 🧠✨
|
|
13
|
+
|
|
14
|
+
## *agentvis* reasoning visualization example
|
|
15
|
+
|
|
16
|
+

|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
Install core:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install agentvis
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
With LangChain support:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install agentvis[langchain]
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
## How to generate an agent reasoning graph
|
|
34
|
+
|
|
35
|
+
### 1. Define your LangChain / LangGraph agent
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
from langchain_tavily import TavilySearch
|
|
39
|
+
from langchain_google_genai import ChatGoogleGenerativeAI
|
|
40
|
+
from langchain.agents import create_agent
|
|
41
|
+
from langchain.messages import HumanMessage
|
|
42
|
+
import os
|
|
43
|
+
|
|
44
|
+
os.environ["TAVILY_API_KEY"] = "<your-tavily-api-key>"
|
|
45
|
+
|
|
46
|
+
tavily_search = TavilySearch(
|
|
47
|
+
max_results=5,
|
|
48
|
+
search_depth="basic",
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
llm = ChatGoogleGenerativeAI(
|
|
52
|
+
model="gemini-2.5-flash-lite",
|
|
53
|
+
temperature=0.7,
|
|
54
|
+
google_api_key="<your-google-api-key>",
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
agent = create_agent(
|
|
58
|
+
llm,
|
|
59
|
+
[tavily_search],
|
|
60
|
+
system_prompt=(
|
|
61
|
+
"You are a helpful web search agent. "
|
|
62
|
+
"Use the Tavily tool when you need fresh information from the web."
|
|
63
|
+
),
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
result = agent.invoke(
|
|
67
|
+
{"messages": [HumanMessage("Top warm countries and weather of each.")]}
|
|
68
|
+
)
|
|
69
|
+
messages = result["messages"]
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### 2. Build the reasoning graph with *agentvis* and get a shareable link in exchange of messages produced by agent
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
from agentvis.framework.langchain import LangChainAdapter
|
|
76
|
+
from agentvis.core.export import ExportFactory
|
|
77
|
+
|
|
78
|
+
# Convert LLM messages into an AgentGraph
|
|
79
|
+
graph = LangChainAdapter().build_agent_graph(messages)
|
|
80
|
+
|
|
81
|
+
# Export as a short link you can open in the UI
|
|
82
|
+
link = ExportFactory.export_graph(graph=graph, export_strategy="link") # or "json"
|
|
83
|
+
print(link)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
> Open the printed `link` in your browser to inspect the full reasoning trace of the `websearch` agent.
|
|
87
|
+
|
|
88
|
+
## License
|
|
89
|
+
|
|
90
|
+
This project is licensed under the Apache License 2.0.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Top-level package for the AgentVis Python library.
|
|
3
|
+
|
|
4
|
+
The main, framework-agnostic APIs live under:
|
|
5
|
+
|
|
6
|
+
- ``agentvis.core`` – graph model, export helpers, strategies
|
|
7
|
+
- ``agentvis.framework`` – optional framework adapters (e.g. LangChain)
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
__all__ = ["core", "framework"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .main import BusinessLogic
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from agentvis.core.models import Node, Connection
|
|
3
|
+
|
|
4
|
+
class ConnectionCreationStrategy(ABC):
|
|
5
|
+
def __init__(self, nodes_matrix: list[list[Node]]):
|
|
6
|
+
self.nodes_matrix = nodes_matrix
|
|
7
|
+
self.connections = []
|
|
8
|
+
|
|
9
|
+
@abstractmethod
|
|
10
|
+
def create_connections(self) -> None:
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
def get_connections(self) -> list[Connection]:
|
|
14
|
+
return self.connections
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from agentvis.core.connection_creation_strategy.base import ConnectionCreationStrategy
|
|
2
|
+
from agentvis.core.models import Connection
|
|
3
|
+
|
|
4
|
+
class ContextConnectionCreation:
|
|
5
|
+
def __init__(self):
|
|
6
|
+
self.strategy = None
|
|
7
|
+
|
|
8
|
+
def set_strategy(self, strategy: ConnectionCreationStrategy) -> None:
|
|
9
|
+
self.strategy = strategy
|
|
10
|
+
|
|
11
|
+
def create_connections(self) -> None:
|
|
12
|
+
self.strategy.create_connections()
|
|
13
|
+
|
|
14
|
+
def get_connections(self) -> list[Connection]:
|
|
15
|
+
return self.strategy.get_connections()
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from difflib import SequenceMatcher
|
|
2
|
+
|
|
3
|
+
def get_best_match(output_text: str, tool_input: str) -> dict:
|
|
4
|
+
matcher = SequenceMatcher(None, output_text, tool_input)
|
|
5
|
+
blocks = matcher.get_matching_blocks()
|
|
6
|
+
real_blocks = [b for b in blocks if b.size > 0]
|
|
7
|
+
if not real_blocks:
|
|
8
|
+
return None
|
|
9
|
+
|
|
10
|
+
best_block = max(real_blocks, key=lambda b: b.size)
|
|
11
|
+
coverage = best_block.size / len(tool_input)
|
|
12
|
+
|
|
13
|
+
return {
|
|
14
|
+
"start": best_block.a,
|
|
15
|
+
"end": best_block.a + best_block.size,
|
|
16
|
+
"matched_text": output_text[best_block.a: best_block.a + best_block.size].strip(),
|
|
17
|
+
"coverage": coverage,
|
|
18
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from agentvis.core.connection_creation_strategy.base import ConnectionCreationStrategy
|
|
2
|
+
from agentvis.core.models import Connection, MessageType, Node, ConnectionData, ConnectionType, ToolOutputMatchDetails
|
|
3
|
+
from agentvis.core.retriever_strategy import ContextRetrieverStrategy, BM25RetrieverStrategy
|
|
4
|
+
from agentvis.core.connection_creation_strategy.helper import get_best_match
|
|
5
|
+
|
|
6
|
+
class StrategyToolToTool(ConnectionCreationStrategy):
|
|
7
|
+
def __init__(self, nodes_matrix: list[list[Node]]):
|
|
8
|
+
super().__init__(nodes_matrix)
|
|
9
|
+
self.context_retriever = ContextRetrieverStrategy()
|
|
10
|
+
|
|
11
|
+
def create_connections(self) -> None:
|
|
12
|
+
tool_outputs = [] # store tuple (node_id, output)
|
|
13
|
+
|
|
14
|
+
# iterate over each frame
|
|
15
|
+
for frame in self.nodes_matrix:
|
|
16
|
+
for node in frame:
|
|
17
|
+
if node.type != MessageType.ToolMessage.value:
|
|
18
|
+
break
|
|
19
|
+
tool_args = node.data["tool_args"]
|
|
20
|
+
for key,value in tool_args.items():
|
|
21
|
+
if not tool_outputs:
|
|
22
|
+
break
|
|
23
|
+
selected_document = self.context_retriever.retrieve(str(value))
|
|
24
|
+
if selected_document:
|
|
25
|
+
document_index, confidence_score = selected_document.document_index, selected_document.confidence_score
|
|
26
|
+
if confidence_score == 0.0:
|
|
27
|
+
continue
|
|
28
|
+
u,v = tool_outputs[document_index][0],node.id
|
|
29
|
+
tool_output_content = tool_outputs[document_index][1]
|
|
30
|
+
best_match = get_best_match(tool_output_content, value)
|
|
31
|
+
self.connections.append(
|
|
32
|
+
Connection(
|
|
33
|
+
id=f"{u}-{v}",
|
|
34
|
+
source=u,
|
|
35
|
+
target=v,
|
|
36
|
+
data=ConnectionData(
|
|
37
|
+
connection_type=ConnectionType.ToolToTool,
|
|
38
|
+
connection_details=ToolOutputMatchDetails(
|
|
39
|
+
target_tool_arg={key:value},
|
|
40
|
+
source_tool_output_matched_text=best_match["matched_text"] if best_match else "",
|
|
41
|
+
source_tool_ouput_start_index=best_match["start"] if best_match else 0,
|
|
42
|
+
source_tool_ouput_end_index=best_match["end"] if best_match else 0,
|
|
43
|
+
confidence_score=confidence_score
|
|
44
|
+
)
|
|
45
|
+
)
|
|
46
|
+
)
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
for node in frame:
|
|
50
|
+
if node.type != MessageType.ToolMessage.value:
|
|
51
|
+
break
|
|
52
|
+
if node.data["content"]:
|
|
53
|
+
tool_outputs.append((node.id, node.data["content"]))
|
|
54
|
+
self.context_retriever.set_strategy(BM25RetrieverStrategy(documents=[str(tool_output) for _,tool_output in tool_outputs]))
|
|
55
|
+
self.context_retriever.index()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .main import ExportFactory
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from agentvis.core.export.base import ExportStrategy
|
|
2
|
+
from agentvis.core.models import AgentGraph
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
class JSONExportStrategy(ExportStrategy):
|
|
6
|
+
def __init__(self, graph: AgentGraph):
|
|
7
|
+
super().__init__(graph)
|
|
8
|
+
|
|
9
|
+
def export(self) -> str:
|
|
10
|
+
return json.dumps(
|
|
11
|
+
{"frames": [frame.model_dump() for frame in self.graph.frames], "connections": [connection.model_dump() for connection in self.graph.connections]},
|
|
12
|
+
separators=(",", ":")
|
|
13
|
+
)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from agentvis.core.export.base import ExportStrategy
|
|
2
|
+
from agentvis.core.models import AgentGraph
|
|
3
|
+
import json
|
|
4
|
+
import zlib
|
|
5
|
+
import base64
|
|
6
|
+
|
|
7
|
+
class LinkExportStrategy(ExportStrategy):
|
|
8
|
+
def __init__(self, graph: AgentGraph):
|
|
9
|
+
super().__init__(graph)
|
|
10
|
+
self.BASE_URL = "https://agentvis.vercel.app"
|
|
11
|
+
|
|
12
|
+
def export(self) -> str:
|
|
13
|
+
data = {
|
|
14
|
+
"frames": [frame.model_dump() for frame in self.graph.frames],
|
|
15
|
+
"connections": [
|
|
16
|
+
connection.model_dump()
|
|
17
|
+
for connection in self.graph.connections
|
|
18
|
+
],
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
json_string = json.dumps(data, separators=(",", ":"))
|
|
22
|
+
compressed = zlib.compress(json_string.encode("utf-8"))
|
|
23
|
+
encoded = base64.urlsafe_b64encode(compressed).decode("utf-8")
|
|
24
|
+
|
|
25
|
+
return f"{self.BASE_URL}?view={encoded}"
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from agentvis.core.models import AgentGraph
|
|
2
|
+
from typing import Literal
|
|
3
|
+
from agentvis.core.export.json import JSONExportStrategy
|
|
4
|
+
from agentvis.core.export.link import LinkExportStrategy
|
|
5
|
+
|
|
6
|
+
class ExportFactory:
|
|
7
|
+
@staticmethod
|
|
8
|
+
def export_graph(graph: AgentGraph, export_strategy: Literal["json", "link"] = "json") -> str:
|
|
9
|
+
match export_strategy:
|
|
10
|
+
case "json":
|
|
11
|
+
return JSONExportStrategy(graph).export()
|
|
12
|
+
case "link":
|
|
13
|
+
return LinkExportStrategy(graph).export()
|
|
14
|
+
case _:
|
|
15
|
+
raise ValueError(f"Invalid export strategy: {export_strategy}")
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
from agentvis.core.models import LLMMessage, Frame, Node, MessageType, Connection
|
|
2
|
+
from agentvis.core.connection_creation_strategy import ContextConnectionCreation, StrategyToolToTool
|
|
3
|
+
from collections import defaultdict
|
|
4
|
+
|
|
5
|
+
class BusinessLogic:
|
|
6
|
+
@staticmethod
|
|
7
|
+
def build_frames(messages: list[LLMMessage]) -> list[Frame]:
|
|
8
|
+
frames = []
|
|
9
|
+
tool_msg_start_idx, tool_msg_end_idx = -1, -1
|
|
10
|
+
function_to_args_map = defaultdict(dict)
|
|
11
|
+
for idx, message in enumerate(messages):
|
|
12
|
+
# if tool message, store the start and end index of the tool message
|
|
13
|
+
if message.type == MessageType.ToolMessage.value:
|
|
14
|
+
if tool_msg_start_idx == -1:
|
|
15
|
+
tool_msg_start_idx = idx
|
|
16
|
+
tool_msg_end_idx = idx
|
|
17
|
+
else:
|
|
18
|
+
if tool_msg_start_idx != -1 and tool_msg_end_idx != -1:
|
|
19
|
+
tool_nodes = [Node(id=tool_msg.id, type=tool_msg.type, data={"content": tool_msg.content, "tool_name": tool_msg.tool_name, "tool_args": function_to_args_map[tool_msg.tool_call_id][tool_msg.tool_name]}) for tool_msg in messages[tool_msg_start_idx:tool_msg_end_idx+1]]
|
|
20
|
+
frames.append(Frame(nodes=tool_nodes))
|
|
21
|
+
tool_msg_start_idx, tool_msg_end_idx = -1, -1
|
|
22
|
+
function_to_args_map = defaultdict(dict)
|
|
23
|
+
|
|
24
|
+
# simply add the frame
|
|
25
|
+
frames.append(Frame(
|
|
26
|
+
nodes=[Node(
|
|
27
|
+
id=message.id,
|
|
28
|
+
type=message.type,
|
|
29
|
+
data={"content": message.content}
|
|
30
|
+
)]
|
|
31
|
+
))
|
|
32
|
+
|
|
33
|
+
# if ai message, store the function calls
|
|
34
|
+
if message.type == MessageType.AIMessage.value:
|
|
35
|
+
if message.tool_calls:
|
|
36
|
+
for tool_call in message.tool_calls:
|
|
37
|
+
function_to_args_map[tool_call.tool_call_id] = {tool_call.name: tool_call.args}
|
|
38
|
+
|
|
39
|
+
return frames
|
|
40
|
+
|
|
41
|
+
@staticmethod
|
|
42
|
+
def create_connections(frames: list[Frame]) -> list[Connection]:
|
|
43
|
+
nodes_matrix = []
|
|
44
|
+
|
|
45
|
+
for frame in frames:
|
|
46
|
+
nodes_matrix.append(frame.nodes)
|
|
47
|
+
|
|
48
|
+
context_connection_creation = ContextConnectionCreation()
|
|
49
|
+
context_connection_creation.set_strategy(StrategyToolToTool(nodes_matrix))
|
|
50
|
+
context_connection_creation.create_connections()
|
|
51
|
+
return context_connection_creation.get_connections()
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
from pydantic import BaseModel
|
|
3
|
+
|
|
4
|
+
# models for tree structure
|
|
5
|
+
class MessageType(str, Enum):
|
|
6
|
+
AIMessage = "AIMessage"
|
|
7
|
+
ToolMessage = "ToolMessage"
|
|
8
|
+
HumanMessage = "HumanMessage"
|
|
9
|
+
SystemMessage = "SystemMessage"
|
|
10
|
+
|
|
11
|
+
class Node(BaseModel):
|
|
12
|
+
id: str
|
|
13
|
+
type: MessageType
|
|
14
|
+
data: dict = {}
|
|
15
|
+
|
|
16
|
+
class ConnectionType(str, Enum):
|
|
17
|
+
ToolToTool = "ToolToTool"
|
|
18
|
+
|
|
19
|
+
class ToolOutputMatchDetails(BaseModel):
|
|
20
|
+
target_tool_arg: dict = {}
|
|
21
|
+
source_tool_output_matched_text: str = ""
|
|
22
|
+
source_tool_ouput_start_index: int = 0
|
|
23
|
+
source_tool_ouput_end_index: int = 0
|
|
24
|
+
confidence_score: float = 0.0
|
|
25
|
+
|
|
26
|
+
class ConnectionData(BaseModel):
|
|
27
|
+
connection_type: ConnectionType
|
|
28
|
+
connection_details: ToolOutputMatchDetails = None
|
|
29
|
+
|
|
30
|
+
class Connection(BaseModel):
|
|
31
|
+
id: str
|
|
32
|
+
source: str
|
|
33
|
+
target: str
|
|
34
|
+
data: ConnectionData = None
|
|
35
|
+
|
|
36
|
+
class Frame(BaseModel):
|
|
37
|
+
nodes: list[Node]
|
|
38
|
+
|
|
39
|
+
class AgentGraph(BaseModel):
|
|
40
|
+
frames: list[Frame]
|
|
41
|
+
connections: list[Connection]
|
|
42
|
+
|
|
43
|
+
# models for LLM messages
|
|
44
|
+
class ToolCall(BaseModel):
|
|
45
|
+
name: str
|
|
46
|
+
args: dict
|
|
47
|
+
tool_call_id: str
|
|
48
|
+
|
|
49
|
+
class LLMMessage(BaseModel):
|
|
50
|
+
id: str
|
|
51
|
+
type: MessageType
|
|
52
|
+
content: str = ""
|
|
53
|
+
tool_calls: list[ToolCall] = []
|
|
54
|
+
tool_name: str = ""
|
|
55
|
+
tool_call_id: str = ""
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from agentvis.core.retriever_strategy.models import SelectedDocument
|
|
3
|
+
|
|
4
|
+
class RetrieverStrategy(ABC):
|
|
5
|
+
def __init__(self, documents: list[str]):
|
|
6
|
+
self.documents = documents
|
|
7
|
+
self.retriever = None
|
|
8
|
+
|
|
9
|
+
@abstractmethod
|
|
10
|
+
def index(self):
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
@abstractmethod
|
|
14
|
+
def retrieve(self, query: str, k: int = 1) -> SelectedDocument:
|
|
15
|
+
pass
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import bm25s
|
|
2
|
+
from agentvis.core.retriever_strategy.base import RetrieverStrategy
|
|
3
|
+
from agentvis.core.retriever_strategy.models import SelectedDocument
|
|
4
|
+
|
|
5
|
+
class BM25RetrieverStrategy(RetrieverStrategy):
|
|
6
|
+
def __init__(self, documents: list[str]):
|
|
7
|
+
super().__init__(documents)
|
|
8
|
+
|
|
9
|
+
def tokenize(self, text: str) -> list[str]:
|
|
10
|
+
return text.lower().strip().split()
|
|
11
|
+
|
|
12
|
+
def index(self):
|
|
13
|
+
self.retriever = bm25s.BM25()
|
|
14
|
+
tokenized_corpus = [self.tokenize(doc) for doc in self.documents]
|
|
15
|
+
self.retriever.index(tokenized_corpus)
|
|
16
|
+
|
|
17
|
+
def retrieve(self, query: str, k: int = 1) -> SelectedDocument:
|
|
18
|
+
try:
|
|
19
|
+
return SelectedDocument(
|
|
20
|
+
document_index=int(self.retriever.retrieve([self.tokenize(query)], k=k).documents[0][0]),
|
|
21
|
+
confidence_score=float(self.retriever.retrieve([self.tokenize(query)], k=k).scores[0][0])
|
|
22
|
+
)
|
|
23
|
+
except IndexError:
|
|
24
|
+
return None
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from agentvis.core.retriever_strategy.base import RetrieverStrategy
|
|
2
|
+
from agentvis.core.retriever_strategy.models import SelectedDocument
|
|
3
|
+
|
|
4
|
+
class ContextRetrieverStrategy:
|
|
5
|
+
def __init__(self):
|
|
6
|
+
self.strategy = None
|
|
7
|
+
|
|
8
|
+
def set_strategy(self, strategy: RetrieverStrategy):
|
|
9
|
+
self.strategy = strategy
|
|
10
|
+
|
|
11
|
+
def index(self):
|
|
12
|
+
self.strategy.index()
|
|
13
|
+
|
|
14
|
+
def retrieve(self, query: str, k: int = 1) -> SelectedDocument:
|
|
15
|
+
return self.strategy.retrieve(query, k)
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .main import LangChainAdapter
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from agentvis.framework.base import AIFrameWork
|
|
2
|
+
from agentvis.core.models import LLMMessage, Frame, ToolCall, AgentGraph
|
|
3
|
+
from agentvis.core import BusinessLogic
|
|
4
|
+
from langchain.messages import ToolMessage, AIMessage, HumanMessage, SystemMessage
|
|
5
|
+
|
|
6
|
+
class LangChainAdapter(AIFrameWork):
|
|
7
|
+
def __init__(self):
|
|
8
|
+
super().__init__()
|
|
9
|
+
|
|
10
|
+
def build_agent_graph(self, messages: list[ToolMessage | AIMessage | HumanMessage | SystemMessage]) -> AgentGraph:
|
|
11
|
+
_messages: list[LLMMessage] = []
|
|
12
|
+
|
|
13
|
+
# convert messages to LLMMessage
|
|
14
|
+
for message in messages:
|
|
15
|
+
tool_calls = []
|
|
16
|
+
tool_name = ""
|
|
17
|
+
tool_call_id = ""
|
|
18
|
+
|
|
19
|
+
if isinstance(message, AIMessage):
|
|
20
|
+
tool_calls = [ToolCall(name=tool_call["name"], args=tool_call["args"], tool_call_id=tool_call["id"]) for tool_call in message.tool_calls]
|
|
21
|
+
if isinstance(message, ToolMessage):
|
|
22
|
+
tool_name = message.name
|
|
23
|
+
tool_call_id = message.tool_call_id
|
|
24
|
+
|
|
25
|
+
_messages.append(LLMMessage(
|
|
26
|
+
id=message.id,
|
|
27
|
+
type=message.__class__.__name__,
|
|
28
|
+
content=message.content,
|
|
29
|
+
tool_calls=tool_calls,
|
|
30
|
+
tool_name=tool_name,
|
|
31
|
+
tool_call_id=tool_call_id
|
|
32
|
+
))
|
|
33
|
+
|
|
34
|
+
frames = BusinessLogic.build_frames(_messages)
|
|
35
|
+
connections = BusinessLogic.create_connections(frames)
|
|
36
|
+
return AgentGraph(frames=frames, connections=connections)
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agentvis
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Framework-agnostic reasoning trace visualization tool for AI agents.
|
|
5
|
+
Author: Nitesh Kumar
|
|
6
|
+
License: Apache License 2.0
|
|
7
|
+
Requires-Python: >=3.9
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: bm25s==0.3.0
|
|
10
|
+
Requires-Dist: pydantic>=2.0.0
|
|
11
|
+
Provides-Extra: langchain
|
|
12
|
+
Requires-Dist: langchain>=0.1.0; extra == "langchain"
|
|
13
|
+
|
|
14
|
+
# agentvis
|
|
15
|
+
|
|
16
|
+
<p align="center">
|
|
17
|
+
<picture align="center">
|
|
18
|
+
<img align="center" alt="agentvis logo" src="https://raw.githubusercontent.com/kumarniteshpersonal-beep/agentvis/968caea6e04577583d7d16f7015b9925362d5f53/packages/ui/public/agentvis_logo.svg" width="560">
|
|
19
|
+
</picture>
|
|
20
|
+
</p>
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
*agentvis* visualizes an agent’s reasoning trace, giving you clear insight into *why* the agent chose a particular path or triggered a specific tool call.
|
|
24
|
+
|
|
25
|
+
By exposing the influence flow behind each decision, it transforms opaque agent behavior into something understandable and actionable — so you can confidently refine and improve your prompts, which is often the hardest part of building reliable agents. 🧠✨
|
|
26
|
+
|
|
27
|
+
## *agentvis* reasoning visualization example
|
|
28
|
+
|
|
29
|
+

|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
Install core:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pip install agentvis
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
With LangChain support:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install agentvis[langchain]
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
## How to generate an agent reasoning graph
|
|
47
|
+
|
|
48
|
+
### 1. Define your LangChain / LangGraph agent
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from langchain_tavily import TavilySearch
|
|
52
|
+
from langchain_google_genai import ChatGoogleGenerativeAI
|
|
53
|
+
from langchain.agents import create_agent
|
|
54
|
+
from langchain.messages import HumanMessage
|
|
55
|
+
import os
|
|
56
|
+
|
|
57
|
+
os.environ["TAVILY_API_KEY"] = "<your-tavily-api-key>"
|
|
58
|
+
|
|
59
|
+
tavily_search = TavilySearch(
|
|
60
|
+
max_results=5,
|
|
61
|
+
search_depth="basic",
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
llm = ChatGoogleGenerativeAI(
|
|
65
|
+
model="gemini-2.5-flash-lite",
|
|
66
|
+
temperature=0.7,
|
|
67
|
+
google_api_key="<your-google-api-key>",
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
agent = create_agent(
|
|
71
|
+
llm,
|
|
72
|
+
[tavily_search],
|
|
73
|
+
system_prompt=(
|
|
74
|
+
"You are a helpful web search agent. "
|
|
75
|
+
"Use the Tavily tool when you need fresh information from the web."
|
|
76
|
+
),
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
result = agent.invoke(
|
|
80
|
+
{"messages": [HumanMessage("Top warm countries and weather of each.")]}
|
|
81
|
+
)
|
|
82
|
+
messages = result["messages"]
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### 2. Build the reasoning graph with *agentvis* and get a shareable link in exchange of messages produced by agent
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
from agentvis.framework.langchain import LangChainAdapter
|
|
89
|
+
from agentvis.core.export import ExportFactory
|
|
90
|
+
|
|
91
|
+
# Convert LLM messages into an AgentGraph
|
|
92
|
+
graph = LangChainAdapter().build_agent_graph(messages)
|
|
93
|
+
|
|
94
|
+
# Export as a short link you can open in the UI
|
|
95
|
+
link = ExportFactory.export_graph(graph=graph, export_strategy="link") # or "json"
|
|
96
|
+
print(link)
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
> Open the printed `link` in your browser to inspect the full reasoning trace of the `websearch` agent.
|
|
100
|
+
|
|
101
|
+
## License
|
|
102
|
+
|
|
103
|
+
This project is licensed under the Apache License 2.0.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
agentvis/__init__.py
|
|
4
|
+
agentvis.egg-info/PKG-INFO
|
|
5
|
+
agentvis.egg-info/SOURCES.txt
|
|
6
|
+
agentvis.egg-info/dependency_links.txt
|
|
7
|
+
agentvis.egg-info/requires.txt
|
|
8
|
+
agentvis.egg-info/top_level.txt
|
|
9
|
+
agentvis/core/__init__.py
|
|
10
|
+
agentvis/core/main.py
|
|
11
|
+
agentvis/core/models.py
|
|
12
|
+
agentvis/core/connection_creation_strategy/__init__.py
|
|
13
|
+
agentvis/core/connection_creation_strategy/base.py
|
|
14
|
+
agentvis/core/connection_creation_strategy/context.py
|
|
15
|
+
agentvis/core/connection_creation_strategy/helper.py
|
|
16
|
+
agentvis/core/connection_creation_strategy/strategy_tool_to_tool.py
|
|
17
|
+
agentvis/core/export/__init__.py
|
|
18
|
+
agentvis/core/export/base.py
|
|
19
|
+
agentvis/core/export/json.py
|
|
20
|
+
agentvis/core/export/link.py
|
|
21
|
+
agentvis/core/export/main.py
|
|
22
|
+
agentvis/core/retriever_strategy/__init__.py
|
|
23
|
+
agentvis/core/retriever_strategy/base.py
|
|
24
|
+
agentvis/core/retriever_strategy/bm25.py
|
|
25
|
+
agentvis/core/retriever_strategy/context.py
|
|
26
|
+
agentvis/core/retriever_strategy/models.py
|
|
27
|
+
agentvis/framework/__init__.py
|
|
28
|
+
agentvis/framework/base.py
|
|
29
|
+
agentvis/framework/langchain/__init__.py
|
|
30
|
+
agentvis/framework/langchain/main.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
agentvis
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "agentvis"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Framework-agnostic reasoning trace visualization tool for AI agents."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = { text = "Apache License 2.0" }
|
|
7
|
+
requires-python = ">=3.9"
|
|
8
|
+
|
|
9
|
+
authors = [
|
|
10
|
+
{ name = "Nitesh Kumar" }
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
dependencies = [
|
|
14
|
+
"bm25s==0.3.0",
|
|
15
|
+
"pydantic>=2.0.0",
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
[project.optional-dependencies]
|
|
19
|
+
langchain = ["langchain>=0.1.0"]
|
|
20
|
+
|
|
21
|
+
[build-system]
|
|
22
|
+
requires = ["setuptools>=61", "wheel"]
|
|
23
|
+
build-backend = "setuptools.build_meta"
|
|
24
|
+
|
|
25
|
+
[tool.setuptools.packages.find]
|
|
26
|
+
where = ["."]
|
|
27
|
+
include = ["agentvis*"]
|
agentvis-0.1.0/setup.cfg
ADDED