10xgraph-api 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.
- 10xgraph_api-0.7.0.dist-info/METADATA +618 -0
- 10xgraph_api-0.7.0.dist-info/RECORD +203 -0
- 10xgraph_api-0.7.0.dist-info/WHEEL +5 -0
- 10xgraph_api-0.7.0.dist-info/entry_points.txt +3 -0
- 10xgraph_api-0.7.0.dist-info/licenses/LICENSE +21 -0
- 10xgraph_api-0.7.0.dist-info/top_level.txt +2 -0
- agentflow_cli/__init__.py +69 -0
- tenxgraph_api/__init__.py +23 -0
- tenxgraph_api/cli/__init__.py +6 -0
- tenxgraph_api/cli/capabilities.py +127 -0
- tenxgraph_api/cli/commands/__init__.py +49 -0
- tenxgraph_api/cli/commands/api.py +262 -0
- tenxgraph_api/cli/commands/audit.py +213 -0
- tenxgraph_api/cli/commands/build.py +305 -0
- tenxgraph_api/cli/commands/config.py +71 -0
- tenxgraph_api/cli/commands/demo.py +93 -0
- tenxgraph_api/cli/commands/eval.py +909 -0
- tenxgraph_api/cli/commands/init.py +531 -0
- tenxgraph_api/cli/commands/skills.py +532 -0
- tenxgraph_api/cli/commands/test.py +113 -0
- tenxgraph_api/cli/commands/version.py +47 -0
- tenxgraph_api/cli/config_editor/__init__.py +15 -0
- tenxgraph_api/cli/config_editor/schema.py +566 -0
- tenxgraph_api/cli/config_editor/server.py +240 -0
- tenxgraph_api/cli/config_editor/static/app.css +2 -0
- tenxgraph_api/cli/config_editor/static/app.js +3 -0
- tenxgraph_api/cli/config_editor/static/favicon.svg +11 -0
- tenxgraph_api/cli/config_editor/static/index.html +20 -0
- tenxgraph_api/cli/config_editor/store.py +120 -0
- tenxgraph_api/cli/config_editor/validation.py +377 -0
- tenxgraph_api/cli/constants.py +112 -0
- tenxgraph_api/cli/context.py +46 -0
- tenxgraph_api/cli/core/__init__.py +0 -0
- tenxgraph_api/cli/core/animations.py +550 -0
- tenxgraph_api/cli/core/config.py +279 -0
- tenxgraph_api/cli/core/output.py +625 -0
- tenxgraph_api/cli/core/prompts.py +220 -0
- tenxgraph_api/cli/core/screen.py +287 -0
- tenxgraph_api/cli/core/steps.py +621 -0
- tenxgraph_api/cli/core/theme.py +242 -0
- tenxgraph_api/cli/core/validation.py +251 -0
- tenxgraph_api/cli/exceptions.py +129 -0
- tenxgraph_api/cli/logger.py +84 -0
- tenxgraph_api/cli/main.py +936 -0
- tenxgraph_api/cli/templates/__init__.py +0 -0
- tenxgraph_api/cli/templates/defaults.py +844 -0
- tenxgraph_api/cli/templates/dev/.env.example +1 -0
- tenxgraph_api/cli/templates/dev/10xgraph.json +16 -0
- tenxgraph_api/cli/templates/dev/graph/__init__.py +0 -0
- tenxgraph_api/cli/templates/dev/graph/agent.py +63 -0
- tenxgraph_api/cli/templates/prod/.env.example +75 -0
- tenxgraph_api/cli/templates/prod/.pre-commit-config.yaml +50 -0
- tenxgraph_api/cli/templates/prod/.python-version +1 -0
- tenxgraph_api/cli/templates/prod/10xgraph.json +42 -0
- tenxgraph_api/cli/templates/prod/auth/__init__.py +0 -0
- tenxgraph_api/cli/templates/prod/auth/agent_auth.py +46 -0
- tenxgraph_api/cli/templates/prod/evals/__init__.py +0 -0
- tenxgraph_api/cli/templates/prod/evals/user_simulator_eval.py +76 -0
- tenxgraph_api/cli/templates/prod/evals/weather_agents_eval.py +53 -0
- tenxgraph_api/cli/templates/prod/graph/__init__.py +0 -0
- tenxgraph_api/cli/templates/prod/graph/agent.py +50 -0
- tenxgraph_api/cli/templates/prod/graph/state.py +5 -0
- tenxgraph_api/cli/templates/prod/graph/thread_name_generator.py +7 -0
- tenxgraph_api/cli/templates/prod/graph/tools/__init__.py +0 -0
- tenxgraph_api/cli/templates/prod/graph/tools/weather_tool.py +43 -0
- tenxgraph_api/cli/templates/prod/graph/validators/__init__.py +0 -0
- tenxgraph_api/cli/templates/prod/graph/validators/lifecyle.py +107 -0
- tenxgraph_api/cli/templates/prod/graph/validators/manager.py +9 -0
- tenxgraph_api/cli/templates/prod/graph/validators/validators.py +21 -0
- tenxgraph_api/cli/templates/prod/pyproject.toml +79 -0
- tenxgraph_api/cli/templates/prod/tests/__init__.py +0 -0
- tenxgraph_api/cli/templates/prod/tests/conftest.py +6 -0
- tenxgraph_api/cli/templates/prod/tests/test_agent_eval.py +78 -0
- tenxgraph_api/cli/templates/prod/tests/test_catalog_tools.py +47 -0
- tenxgraph_api/cli/templates/prod/tests/test_graph_nodes.py +57 -0
- tenxgraph_api/cli/templates/skills/10xgraph/SKILL.md +92 -0
- tenxgraph_api/cli/templates/skills/10xgraph/references/agents-and-tools.md +389 -0
- tenxgraph_api/cli/templates/skills/10xgraph/references/api-client.md +61 -0
- tenxgraph_api/cli/templates/skills/10xgraph/references/api-configuration.md +75 -0
- tenxgraph_api/cli/templates/skills/10xgraph/references/api-settings-and-middleware.md +89 -0
- tenxgraph_api/cli/templates/skills/10xgraph/references/architecture.md +91 -0
- tenxgraph_api/cli/templates/skills/10xgraph/references/auth-and-authorization.md +149 -0
- tenxgraph_api/cli/templates/skills/10xgraph/references/callbacks-and-command.md +283 -0
- tenxgraph_api/cli/templates/skills/10xgraph/references/checkpointing-and-threads.md +63 -0
- tenxgraph_api/cli/templates/skills/10xgraph/references/cli-commands.md +73 -0
- tenxgraph_api/cli/templates/skills/10xgraph/references/client-auth-and-errors.md +94 -0
- tenxgraph_api/cli/templates/skills/10xgraph/references/client-messages-invoke-stream.md +115 -0
- tenxgraph_api/cli/templates/skills/10xgraph/references/client-threads-memory-files.md +80 -0
- tenxgraph_api/cli/templates/skills/10xgraph/references/context-id-background.md +80 -0
- tenxgraph_api/cli/templates/skills/10xgraph/references/dependency-injection.md +58 -0
- tenxgraph_api/cli/templates/skills/10xgraph/references/evaluation.md +500 -0
- tenxgraph_api/cli/templates/skills/10xgraph/references/id-and-thread-name-generators.md +69 -0
- tenxgraph_api/cli/templates/skills/10xgraph/references/media-and-files.md +66 -0
- tenxgraph_api/cli/templates/skills/10xgraph/references/memory-and-store.md +70 -0
- tenxgraph_api/cli/templates/skills/10xgraph/references/prebuilt-agents-and-tools.md +394 -0
- tenxgraph_api/cli/templates/skills/10xgraph/references/production-runtime.md +56 -0
- tenxgraph_api/cli/templates/skills/10xgraph/references/providers-and-adapters.md +89 -0
- tenxgraph_api/cli/templates/skills/10xgraph/references/publishers-and-runtime-protocols.md +84 -0
- tenxgraph_api/cli/templates/skills/10xgraph/references/rate-limiting.md +194 -0
- tenxgraph_api/cli/templates/skills/10xgraph/references/realtime.md +172 -0
- tenxgraph_api/cli/templates/skills/10xgraph/references/remote-tools.md +55 -0
- tenxgraph_api/cli/templates/skills/10xgraph/references/rest-api-and-errors.md +97 -0
- tenxgraph_api/cli/templates/skills/10xgraph/references/security-and-validators.md +86 -0
- tenxgraph_api/cli/templates/skills/10xgraph/references/state-and-messages.md +68 -0
- tenxgraph_api/cli/templates/skills/10xgraph/references/state-graph.md +232 -0
- tenxgraph_api/cli/templates/skills/10xgraph/references/stream-emitter.md +144 -0
- tenxgraph_api/cli/templates/skills/10xgraph/references/streaming.md +53 -0
- tenxgraph_api/cli/templates/skills/10xgraph/references/testing-and-evaluation.md +64 -0
- tenxgraph_api/cli/templates/skills/10xgraph/references/unit-testing.md +202 -0
- tenxgraph_api/cli/templates/skills/copilot/10xgraph.instructions.md +108 -0
- tenxgraph_api/py.typed +0 -0
- tenxgraph_api/src/__init__.py +0 -0
- tenxgraph_api/src/app/__init__.py +13 -0
- tenxgraph_api/src/app/core/__init__.py +15 -0
- tenxgraph_api/src/app/core/auth/__init__.py +0 -0
- tenxgraph_api/src/app/core/auth/auth_backend.py +38 -0
- tenxgraph_api/src/app/core/auth/authorization.py +379 -0
- tenxgraph_api/src/app/core/auth/base_auth.py +27 -0
- tenxgraph_api/src/app/core/auth/jwt_auth.py +122 -0
- tenxgraph_api/src/app/core/auth/ownership_resolver.py +170 -0
- tenxgraph_api/src/app/core/auth/permissions.py +354 -0
- tenxgraph_api/src/app/core/auth/request_config.py +130 -0
- tenxgraph_api/src/app/core/auth/route_guard.py +105 -0
- tenxgraph_api/src/app/core/config/__init__.py +0 -0
- tenxgraph_api/src/app/core/config/graph_config.py +532 -0
- tenxgraph_api/src/app/core/config/media_settings.py +72 -0
- tenxgraph_api/src/app/core/config/sentry_config.py +60 -0
- tenxgraph_api/src/app/core/config/settings.py +202 -0
- tenxgraph_api/src/app/core/config/setup_logs.py +65 -0
- tenxgraph_api/src/app/core/config/setup_middleware.py +483 -0
- tenxgraph_api/src/app/core/config/worker_middleware.py +99 -0
- tenxgraph_api/src/app/core/exceptions/__init__.py +17 -0
- tenxgraph_api/src/app/core/exceptions/general_exception.py +58 -0
- tenxgraph_api/src/app/core/exceptions/handle_errors.py +363 -0
- tenxgraph_api/src/app/core/exceptions/resources_exceptions.py +72 -0
- tenxgraph_api/src/app/core/exceptions/user_exception.py +58 -0
- tenxgraph_api/src/app/core/middleware/__init__.py +25 -0
- tenxgraph_api/src/app/core/middleware/rate_limit/__init__.py +15 -0
- tenxgraph_api/src/app/core/middleware/rate_limit/base.py +27 -0
- tenxgraph_api/src/app/core/middleware/rate_limit/factory.py +98 -0
- tenxgraph_api/src/app/core/middleware/rate_limit/keying.py +133 -0
- tenxgraph_api/src/app/core/middleware/rate_limit/memory.py +98 -0
- tenxgraph_api/src/app/core/middleware/rate_limit/middleware.py +90 -0
- tenxgraph_api/src/app/core/middleware/rate_limit/redis.py +115 -0
- tenxgraph_api/src/app/core/middleware/request_limits.py +154 -0
- tenxgraph_api/src/app/core/middleware/security_headers.py +239 -0
- tenxgraph_api/src/app/core/utils/__init__.py +14 -0
- tenxgraph_api/src/app/core/utils/log_sanitizer.py +216 -0
- tenxgraph_api/src/app/loader.py +516 -0
- tenxgraph_api/src/app/main.py +180 -0
- tenxgraph_api/src/app/routers/__init__.py +4 -0
- tenxgraph_api/src/app/routers/ag_ui/__init__.py +10 -0
- tenxgraph_api/src/app/routers/ag_ui/converter.py +195 -0
- tenxgraph_api/src/app/routers/ag_ui/event_mapper.py +353 -0
- tenxgraph_api/src/app/routers/ag_ui/router.py +90 -0
- tenxgraph_api/src/app/routers/ag_ui/service.py +193 -0
- tenxgraph_api/src/app/routers/checkpointer/__init__.py +1 -0
- tenxgraph_api/src/app/routers/checkpointer/router.py +482 -0
- tenxgraph_api/src/app/routers/checkpointer/schemas/__init__.py +1 -0
- tenxgraph_api/src/app/routers/checkpointer/schemas/checkpointer_schemas.py +121 -0
- tenxgraph_api/src/app/routers/checkpointer/services/__init__.py +1 -0
- tenxgraph_api/src/app/routers/checkpointer/services/checkpointer_service.py +300 -0
- tenxgraph_api/src/app/routers/evals/__init__.py +6 -0
- tenxgraph_api/src/app/routers/evals/router.py +38 -0
- tenxgraph_api/src/app/routers/evals/services/__init__.py +1 -0
- tenxgraph_api/src/app/routers/evals/services/eval_report_service.py +354 -0
- tenxgraph_api/src/app/routers/graph/__init__.py +6 -0
- tenxgraph_api/src/app/routers/graph/realtime_guard.py +161 -0
- tenxgraph_api/src/app/routers/graph/router.py +821 -0
- tenxgraph_api/src/app/routers/graph/schemas/__init__.py +1 -0
- tenxgraph_api/src/app/routers/graph/schemas/graph_schemas.py +386 -0
- tenxgraph_api/src/app/routers/graph/services/__init__.py +1 -0
- tenxgraph_api/src/app/routers/graph/services/graph_service.py +1219 -0
- tenxgraph_api/src/app/routers/graph/services/multimodal_preprocessor.py +164 -0
- tenxgraph_api/src/app/routers/media/__init__.py +506 -0
- tenxgraph_api/src/app/routers/media/router.py +242 -0
- tenxgraph_api/src/app/routers/media/schemas.py +39 -0
- tenxgraph_api/src/app/routers/ping/__init__.py +4 -0
- tenxgraph_api/src/app/routers/ping/router.py +28 -0
- tenxgraph_api/src/app/routers/setup_router.py +60 -0
- tenxgraph_api/src/app/routers/store/__init__.py +4 -0
- tenxgraph_api/src/app/routers/store/router.py +200 -0
- tenxgraph_api/src/app/routers/store/schemas/__init__.py +28 -0
- tenxgraph_api/src/app/routers/store/schemas/store_schemas.py +200 -0
- tenxgraph_api/src/app/routers/store/services/__init__.py +4 -0
- tenxgraph_api/src/app/routers/store/services/store_service.py +203 -0
- tenxgraph_api/src/app/tasks/__init__.py +0 -0
- tenxgraph_api/src/app/tasks/user_tasks.py +38 -0
- tenxgraph_api/src/app/utils/__init__.py +12 -0
- tenxgraph_api/src/app/utils/callable_helper.py +26 -0
- tenxgraph_api/src/app/utils/media/__init__.py +15 -0
- tenxgraph_api/src/app/utils/media/extractor.py +172 -0
- tenxgraph_api/src/app/utils/media/pipeline.py +83 -0
- tenxgraph_api/src/app/utils/parse_output.py +17 -0
- tenxgraph_api/src/app/utils/response_helper.py +129 -0
- tenxgraph_api/src/app/utils/schemas/__init__.py +11 -0
- tenxgraph_api/src/app/utils/schemas/output_schemas.py +28 -0
- tenxgraph_api/src/app/utils/schemas/user_schemas.py +13 -0
- tenxgraph_api/src/app/utils/snowflake_id_generator.py +90 -0
- tenxgraph_api/src/app/utils/swagger_helper.py +160 -0
- tenxgraph_api/src/app/utils/telemetry_store.py +123 -0
- tenxgraph_api/src/app/utils/thread_name_generator.py +304 -0
- tenxgraph_api/src/app/worker.py +91 -0
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
# StateGraph and Nodes
|
|
2
|
+
|
|
3
|
+
Use this when adding workflows, nodes, edges, interrupts, graph compilation, or execution behavior.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Core model
|
|
8
|
+
|
|
9
|
+
`StateGraph` is the workflow engine. A graph describes a directed set of nodes and edges executed over an `AgentState` subclass. The compiled graph manages state loading, routing, checkpointing, streaming, interrupts, recursion limits, and shutdown automatically.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Creating a StateGraph
|
|
14
|
+
|
|
15
|
+
### Minimal
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
from tenxgraph.core.graph import StateGraph
|
|
19
|
+
|
|
20
|
+
graph = StateGraph() # defaults to AgentState
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Custom state class
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
from tenxgraph.core.state import AgentState
|
|
27
|
+
|
|
28
|
+
class OrderState(AgentState):
|
|
29
|
+
order_id: str = ""
|
|
30
|
+
total: float = 0.0
|
|
31
|
+
|
|
32
|
+
graph = StateGraph(OrderState) # pass class or instance — both work
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Full constructor
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
StateGraph(
|
|
39
|
+
state: StateT | None = None,
|
|
40
|
+
context_manager: BaseContextManager | None = None,
|
|
41
|
+
publisher: BasePublisher | None = None,
|
|
42
|
+
id_generator: BaseIDGenerator = DefaultIDGenerator(),
|
|
43
|
+
container: InjectQ | None = None,
|
|
44
|
+
)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
| Parameter | Description |
|
|
48
|
+
|---|---|
|
|
49
|
+
| `state` | State class or instance. Defaults to `AgentState()` |
|
|
50
|
+
| `context_manager` | Optional cross-node state transformer (trim context, summarise, etc.) |
|
|
51
|
+
| `publisher` | Optional publisher for emitting lifecycle events (Kafka, Redis Pub/Sub, RabbitMQ) |
|
|
52
|
+
| `id_generator` | Strategy for generating message and run IDs |
|
|
53
|
+
| `container` | `InjectQ` container for DI. Defaults to the global singleton |
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Adding nodes
|
|
58
|
+
|
|
59
|
+
A node is any callable that receives `AgentState` (or subclass) and returns a `Message`, `ToolResult`, or state `dict`.
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
graph.add_node("MAIN", agent) # explicit name
|
|
63
|
+
graph.add_node("TOOL", tool_node)
|
|
64
|
+
graph.add_node(process) # auto-name: "process"
|
|
65
|
+
graph.override_node("MAIN", mock) # replace existing node — useful in tests
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Edges
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
graph.add_edge("TOOL", "MAIN") # static: always TOOL → MAIN
|
|
74
|
+
graph.set_entry_point("MAIN") # shorthand for add_edge(START, "MAIN")
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Conditional edges
|
|
78
|
+
|
|
79
|
+
The routing function receives `AgentState` and returns a string key. `path_map` maps that key to a node name:
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
from tenxgraph.utils.constants import END
|
|
83
|
+
|
|
84
|
+
def route(state: AgentState) -> str:
|
|
85
|
+
last = state.context[-1]
|
|
86
|
+
if hasattr(last, "tools_calls") and last.tools_calls and last.role == "assistant":
|
|
87
|
+
return "TOOL"
|
|
88
|
+
if last.role == "tool":
|
|
89
|
+
return "MAIN"
|
|
90
|
+
return END
|
|
91
|
+
|
|
92
|
+
graph.add_conditional_edges("MAIN", route, {"TOOL": "TOOL", END: END})
|
|
93
|
+
# If path_map is omitted, the function must return the destination node name directly.
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Use `Command(goto=...)` for runtime jumps only when static or conditional edges are insufficient. See `callbacks-and-command.md`.
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Compiling
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
from tenxgraph.utils import CallbackManager
|
|
104
|
+
|
|
105
|
+
app = graph.compile(
|
|
106
|
+
checkpointer=checkpointer, # BaseCheckpointer | None
|
|
107
|
+
store=store, # BaseStore | None
|
|
108
|
+
media_store=media_store, # BaseMediaStore | None
|
|
109
|
+
interrupt_before=["VALIDATOR"], # pause before these node names
|
|
110
|
+
interrupt_after=["TOOL"], # pause after these node names
|
|
111
|
+
callback_manager=CallbackManager(),
|
|
112
|
+
shutdown_timeout=30.0,
|
|
113
|
+
)
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
| Parameter | Type | Description |
|
|
117
|
+
|---|---|---|
|
|
118
|
+
| `checkpointer` | `BaseCheckpointer \| None` | State persistence. Defaults to `InMemoryCheckpointer` if not provided |
|
|
119
|
+
| `store` | `BaseStore \| None` | Long-term cross-thread memory store |
|
|
120
|
+
| `media_store` | `BaseMediaStore \| None` | Media file storage for multimodal content |
|
|
121
|
+
| `interrupt_before` | `list[str] \| None` | Pause before these node names |
|
|
122
|
+
| `interrupt_after` | `list[str] \| None` | Pause after these node names |
|
|
123
|
+
| `callback_manager` | `CallbackManager` | Hooks for observability and lifecycle events |
|
|
124
|
+
| `shutdown_timeout` | `float` | Seconds to wait for graceful shutdown (default `30.0`) |
|
|
125
|
+
|
|
126
|
+
`compile()` raises `GraphError` if no entry point is set or if interrupt nodes do not exist.
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Invoking
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
from tenxgraph.core.state import Message
|
|
134
|
+
from tenxgraph.utils import ResponseGranularity
|
|
135
|
+
|
|
136
|
+
result = app.invoke(
|
|
137
|
+
{"messages": [Message.text_message("Hello!")]},
|
|
138
|
+
config={"thread_id": "t1", "recursion_limit": 25},
|
|
139
|
+
response_granularity=ResponseGranularity.LOW,
|
|
140
|
+
)
|
|
141
|
+
messages = result["messages"]
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
`ainvoke()` is the async equivalent. Returns the full state dict.
|
|
145
|
+
|
|
146
|
+
### Lifecycle / async context manager
|
|
147
|
+
|
|
148
|
+
`CompiledGraph` supports `async with`; `aclose()` runs on exit even if the body raises, and is
|
|
149
|
+
idempotent (a second call returns `{"status": "already_closed"}`).
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
async with await build_and_compile_graph() as graph:
|
|
153
|
+
await graph.ainvoke(input_data)
|
|
154
|
+
# aclose() runs automatically here
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## Streaming
|
|
160
|
+
|
|
161
|
+
```python
|
|
162
|
+
from tenxgraph.core.state.stream_chunks import StreamEvent
|
|
163
|
+
|
|
164
|
+
for chunk in app.stream(
|
|
165
|
+
{"messages": [Message.text_message("Hello!")]},
|
|
166
|
+
config={"thread_id": "t2"},
|
|
167
|
+
):
|
|
168
|
+
if chunk.event == StreamEvent.MESSAGE and chunk.message:
|
|
169
|
+
print(chunk.message.text())
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
`astream()` is the async equivalent. See `streaming.md` for full details on `StreamChunk` fields and `StreamEvent` values.
|
|
173
|
+
|
|
174
|
+
### Realtime (audio) graphs
|
|
175
|
+
|
|
176
|
+
A graph rooted at a `LiveAgent` is driven by `arealtime(input_queue, config=None, state=None)` (async
|
|
177
|
+
generator of `RealtimeEvent`s) or the sync `realtime(...)` wrapper, not `invoke` / `stream`. The
|
|
178
|
+
forcing rule is mutual: a graph with a `LiveAgent` must use `arealtime()` (`invoke` / `stream`
|
|
179
|
+
raise), and `arealtime()` requires exactly one `LiveAgent` (ordinary graphs raise). See
|
|
180
|
+
`realtime.md`.
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## Config keys
|
|
185
|
+
|
|
186
|
+
Passed in the `config` dict to `invoke` / `stream`:
|
|
187
|
+
|
|
188
|
+
| Key | Type | Description |
|
|
189
|
+
|---|---|---|
|
|
190
|
+
| `thread_id` | `str` | Conversation thread identifier. Required for checkpointing |
|
|
191
|
+
| `user_id` | `str` | Optional user identifier; injected into tool `config` param |
|
|
192
|
+
| `run_id` | `str` | Optional run identifier; auto-generated if omitted |
|
|
193
|
+
| `recursion_limit` | `int` | Max node executions before stopping (default `25`) |
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## Interrupts
|
|
198
|
+
|
|
199
|
+
Compile with `interrupt_before` or `interrupt_after`. When execution reaches an interrupt node, the graph pauses and persists state. Resume by calling `invoke` again with the same `thread_id` and an empty input:
|
|
200
|
+
|
|
201
|
+
```python
|
|
202
|
+
app = graph.compile(checkpointer=checkpointer, interrupt_before=["VALIDATOR"])
|
|
203
|
+
|
|
204
|
+
# First call — pauses before VALIDATOR
|
|
205
|
+
app.invoke({"messages": [Message.text_message("Start")]}, config={"thread_id": "t3"})
|
|
206
|
+
|
|
207
|
+
# Resume after review
|
|
208
|
+
app.invoke({}, config={"thread_id": "t3"})
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## Execution lifecycle
|
|
214
|
+
|
|
215
|
+
```
|
|
216
|
+
app.invoke → Load or create AgentState (from checkpointer if thread_id exists)
|
|
217
|
+
→ Run entry node
|
|
218
|
+
→ Evaluate conditional or static edge
|
|
219
|
+
→ Run next node
|
|
220
|
+
→ Check for interrupt → pause and save state (if configured)
|
|
221
|
+
→ Repeat until END
|
|
222
|
+
→ Save final state → Return result dict
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
## Source map
|
|
228
|
+
|
|
229
|
+
- StateGraph: https://github.com/10xGraph/10xGraph/blob/main/tenxgraph/core/graph/state_graph.py
|
|
230
|
+
- CompiledGraph: https://github.com/10xGraph/10xGraph/blob/main/tenxgraph/core/graph/compiled_graph.py
|
|
231
|
+
- Constants (END, START): https://github.com/10xGraph/10xGraph/blob/main/tenxgraph/utils/constants.py
|
|
232
|
+
- How-to (build a graph): https://10xgraph.com/how-to/python/build-a-graph
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# StreamEmitter
|
|
2
|
+
|
|
3
|
+
Emit live progress, errors, and status updates from tools during streaming execution.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
`StreamEmitter` allows tools to send **live progress updates** back to the caller during `app.stream(...)` / `app.astream(...)` execution. Tools can report intermediate steps, retries, and errors in real-time without using an external publisher.
|
|
8
|
+
|
|
9
|
+
## Key Points
|
|
10
|
+
|
|
11
|
+
- **Streaming only**: Automatically injected during `app.stream()` / `app.astream()`, not during `invoke()` / `ainvoke()`
|
|
12
|
+
- **Optional parameter**: Declare as `emit: StreamEmitter | None = None` in tool functions
|
|
13
|
+
- **Thread-safe**: Works with both sync and async tools
|
|
14
|
+
- **Built-in**: Uses the same streaming pipeline; no external publisher required
|
|
15
|
+
|
|
16
|
+
## Common Methods
|
|
17
|
+
|
|
18
|
+
### `progress(message: str, data: dict | None = None)`
|
|
19
|
+
|
|
20
|
+
Emit a progress update showing intermediate steps or status changes.
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
if emit:
|
|
24
|
+
emit.progress("Fetching data...", data={"attempt": 1, "max_attempts": 3})
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### `error(message: str, data: dict | None = None)`
|
|
28
|
+
|
|
29
|
+
Emit an error update (informational; doesn't interrupt execution).
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
if emit:
|
|
33
|
+
emit.error("API timeout, using cache", data={"retry_count": 3})
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### `message(message: str, data: dict | None = None)`
|
|
37
|
+
|
|
38
|
+
Emit a plain informational message.
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
if emit:
|
|
42
|
+
emit.message("Processing complete", data={"items_processed": 1000})
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### `update(data: dict)`
|
|
46
|
+
|
|
47
|
+
Emit a generic data update without a message.
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
if emit:
|
|
51
|
+
emit.update({
|
|
52
|
+
"status": "batch_progress",
|
|
53
|
+
"processed": 50,
|
|
54
|
+
"total": 100,
|
|
55
|
+
"percentage": 50.0,
|
|
56
|
+
})
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Usage Pattern
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
from tenxgraph.core.state.stream_emitter import StreamEmitter
|
|
63
|
+
|
|
64
|
+
def my_tool(
|
|
65
|
+
param: str,
|
|
66
|
+
emit: StreamEmitter | None = None,
|
|
67
|
+
) -> str:
|
|
68
|
+
"""Tool that reports progress during streaming."""
|
|
69
|
+
if emit:
|
|
70
|
+
emit.progress("Starting work...")
|
|
71
|
+
|
|
72
|
+
# ... do work ...
|
|
73
|
+
|
|
74
|
+
if emit:
|
|
75
|
+
emit.progress("Finalizing...", data={"step": 2})
|
|
76
|
+
|
|
77
|
+
return "result"
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## When to Use
|
|
81
|
+
|
|
82
|
+
✅ **Use for:**
|
|
83
|
+
- Long-running operations (API calls, file processing)
|
|
84
|
+
- Retries with multiple attempts
|
|
85
|
+
- Multi-step processes
|
|
86
|
+
- Batch processing with progress tracking
|
|
87
|
+
|
|
88
|
+
❌ **Don't use for:**
|
|
89
|
+
- Fast operations (<100ms)
|
|
90
|
+
- Non-streaming paths (emit will be None anyway)
|
|
91
|
+
- Critical control flow (always return a result regardless)
|
|
92
|
+
|
|
93
|
+
## Behavior
|
|
94
|
+
|
|
95
|
+
| Execution Mode | Emit Parameter | Output |
|
|
96
|
+
|---|---|---|
|
|
97
|
+
| `app.stream()` | `StreamEmitter` | Progress updates in stream |
|
|
98
|
+
| `app.astream()` | `StreamEmitter` | Progress updates in stream |
|
|
99
|
+
| `app.invoke()` | `None` | No progress updates |
|
|
100
|
+
| `app.ainvoke()` | `None` | No progress updates |
|
|
101
|
+
|
|
102
|
+
## Stream Output
|
|
103
|
+
|
|
104
|
+
Emitted chunks appear in the stream output with structure:
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
{
|
|
108
|
+
"event": "message" | "error" | "update",
|
|
109
|
+
"data": {
|
|
110
|
+
"status": "tool_progress" | "tool_failed" | "tool_message" | ...,
|
|
111
|
+
"tool_name": "my_tool",
|
|
112
|
+
"tool_call_id": "call_abc123",
|
|
113
|
+
"node": "TOOL",
|
|
114
|
+
"message": "...",
|
|
115
|
+
"thread_id": "...",
|
|
116
|
+
"run_id": "...",
|
|
117
|
+
# ... plus any extra data passed
|
|
118
|
+
},
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Performance Tips
|
|
123
|
+
|
|
124
|
+
- Emit at meaningful intervals, not every iteration
|
|
125
|
+
- For batch work: emit every N items, not on every item
|
|
126
|
+
- Avoid thousands of updates per second
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
# ❌ Too frequent
|
|
130
|
+
for item in items:
|
|
131
|
+
if emit:
|
|
132
|
+
emit.progress(f"Processing {item}")
|
|
133
|
+
|
|
134
|
+
# ✅ Batched
|
|
135
|
+
for i, item in enumerate(items):
|
|
136
|
+
if (i + 1) % 100 == 0 and emit:
|
|
137
|
+
emit.progress(f"Processed {i + 1} of {len(items)}")
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## See Also
|
|
141
|
+
|
|
142
|
+
- [Tools](agents-and-tools.md) — Defining and registering tools
|
|
143
|
+
- [Streaming](streaming.md) — Overview of streaming chunks
|
|
144
|
+
- [Dependency Injection](dependency-injection.md) — How parameters like `emit` are injected
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Streaming
|
|
2
|
+
|
|
3
|
+
Use this when working on incremental output, SSE, cancellation, response granularity, or TypeScript async iteration.
|
|
4
|
+
|
|
5
|
+
## Modes
|
|
6
|
+
|
|
7
|
+
- `invoke`: run to completion and return a final result dict.
|
|
8
|
+
- `ainvoke`: async invoke.
|
|
9
|
+
- `stream`: sync generator yielding `StreamChunk`.
|
|
10
|
+
- `astream`: async generator yielding `StreamChunk`.
|
|
11
|
+
|
|
12
|
+
Use streaming for chat UIs and any caller that needs partial output.
|
|
13
|
+
|
|
14
|
+
## StreamChunk
|
|
15
|
+
|
|
16
|
+
Important fields:
|
|
17
|
+
|
|
18
|
+
- `event`: `"message"`, `"state"`, `"error"`, or `"updates"`.
|
|
19
|
+
- `message`: populated for message events.
|
|
20
|
+
- `state`: populated for state events.
|
|
21
|
+
- `data`: populated for errors/updates.
|
|
22
|
+
- `thread_id`, `run_id`, `metadata`, `timestamp`.
|
|
23
|
+
|
|
24
|
+
## ResponseGranularity
|
|
25
|
+
|
|
26
|
+
- `LOW`: latest messages only; good default for UI streaming.
|
|
27
|
+
- `PARTIAL`: context, summary, and latest messages.
|
|
28
|
+
- `FULL`: complete state and messages.
|
|
29
|
+
|
|
30
|
+
Use lower granularity for client performance and privacy unless full state is needed.
|
|
31
|
+
|
|
32
|
+
## Stop
|
|
33
|
+
|
|
34
|
+
Use `app.stop()` / `app.astop()` or `POST /v1/graph/stop` to request cancellation for a thread. The graph exits cleanly after node boundaries.
|
|
35
|
+
|
|
36
|
+
## REST and Client
|
|
37
|
+
|
|
38
|
+
REST:
|
|
39
|
+
|
|
40
|
+
- `POST /v1/graph/stream` returns server-sent events.
|
|
41
|
+
- Each SSE data payload is a serialized `StreamChunk`.
|
|
42
|
+
|
|
43
|
+
TypeScript:
|
|
44
|
+
|
|
45
|
+
- `AgentFlowClient.stream(messages, options)` returns an async generator.
|
|
46
|
+
- Handle `chunk.event === "message"` for incremental text.
|
|
47
|
+
|
|
48
|
+
## Source Map
|
|
49
|
+
|
|
50
|
+
- Stream chunks: https://github.com/10xGraph/10xGraph/blob/main/tenxgraph/core/state/stream_chunks.py
|
|
51
|
+
- Compiled graph streaming: https://github.com/10xGraph/10xGraph/blob/main/tenxgraph/core/graph/compiled_graph.py
|
|
52
|
+
- Graph API router/service: https://github.com/10xGraph/10xgraph-api/tree/main/tenxgraph_api/src/app/routers/graph
|
|
53
|
+
- TS stream endpoint: https://github.com/10xHub/agentflow-client/blob/main/src/endpoints/stream.ts
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Testing and Evaluation
|
|
2
|
+
|
|
3
|
+
Use this when writing tests, mocks, eval datasets, automated quality checks, or trajectory assertions.
|
|
4
|
+
|
|
5
|
+
## Testing Helpers
|
|
6
|
+
|
|
7
|
+
Import from `tenxgraph.qa.testing` or `tenxgraph.qa`.
|
|
8
|
+
|
|
9
|
+
Key helpers:
|
|
10
|
+
|
|
11
|
+
- `TestAgent`: model-free agent double that returns predefined responses and can simulate tool calls.
|
|
12
|
+
- `QuickTest`: quick single-turn, multi-turn, tool, and custom graph tests.
|
|
13
|
+
- `TestResult`: fluent assertions for output text, tool calls, message counts, and errors.
|
|
14
|
+
- `TestContext`: context manager for creating graphs, agents, stores, and mocks.
|
|
15
|
+
- `MockToolRegistry`: register sync/async mock tools and assert calls.
|
|
16
|
+
- `MockMCPClient`: mock MCP tool listing/calls.
|
|
17
|
+
- `MockComposioAdapter` and `MockLangChainAdapter`: test third-party tool adapter behavior.
|
|
18
|
+
- `InMemoryStore`: deterministic memory store for tests.
|
|
19
|
+
|
|
20
|
+
Use these instead of real model/provider calls in unit tests.
|
|
21
|
+
|
|
22
|
+
## Evaluation Framework
|
|
23
|
+
|
|
24
|
+
Import from `tenxgraph.qa.evaluation` or `tenxgraph.qa`.
|
|
25
|
+
|
|
26
|
+
Core dataset/results types:
|
|
27
|
+
|
|
28
|
+
- `EvalCase`, `EvalSet`, `EvalSetBuilder`
|
|
29
|
+
- `ToolCall`, `Invocation`, `TrajectoryStep`, `StepType`
|
|
30
|
+
- `EvalConfig`, `CriterionConfig`, `MatchType`, `Rubric`
|
|
31
|
+
- `EvalReport`, `EvalCaseResult`, `CriterionResult`
|
|
32
|
+
|
|
33
|
+
Runner and shortcuts:
|
|
34
|
+
|
|
35
|
+
- `AgentEvaluator`
|
|
36
|
+
- `QuickEval`
|
|
37
|
+
- `run_eval`
|
|
38
|
+
- `create_eval_app`
|
|
39
|
+
- `create_simple_eval_set`
|
|
40
|
+
- `eval_test`
|
|
41
|
+
- `parametrize_eval_cases`
|
|
42
|
+
|
|
43
|
+
Criteria/reporters:
|
|
44
|
+
|
|
45
|
+
- Tool, trajectory, node-order, response, exact-match, keyword, ROUGE, rubric, safety, hallucination, factual accuracy, LLM judge, and simulation criteria.
|
|
46
|
+
- Console, JSON, HTML, JUnit XML, and reporter manager outputs.
|
|
47
|
+
|
|
48
|
+
## Trajectory Collection
|
|
49
|
+
|
|
50
|
+
Use `TrajectoryCollector` plus `make_trajectory_callback` to record node/tool execution through the callback system. Compile the graph once with the callback manager and reuse it for eval runs.
|
|
51
|
+
|
|
52
|
+
## Rules
|
|
53
|
+
|
|
54
|
+
- Keep unit tests model-free with `TestAgent` and mocks.
|
|
55
|
+
- Use evals for behavior quality, trajectory matching, safety, and regression checks.
|
|
56
|
+
- Avoid mixing live providers into fast unit tests; isolate them as integration tests.
|
|
57
|
+
- For trajectory evals, compile once with the collector callback to avoid losing callback state.
|
|
58
|
+
|
|
59
|
+
## Source Map
|
|
60
|
+
|
|
61
|
+
- Testing package: https://github.com/10xGraph/10xGraph/tree/main/tenxgraph/qa/testing
|
|
62
|
+
- Evaluation package: https://github.com/10xGraph/10xGraph/tree/main/tenxgraph/qa/evaluation
|
|
63
|
+
- Public exports: https://github.com/10xGraph/10xGraph/blob/main/tenxgraph/qa/__init__.py
|
|
64
|
+
- Docs: https://10xgraph.com/
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# Unit Testing
|
|
2
|
+
|
|
3
|
+
Use this when writing fast, deterministic tests for 10xGraph agent graphs without making real LLM API calls. All helpers live in `tenxgraph.qa.testing`.
|
|
4
|
+
|
|
5
|
+
## TestAgent
|
|
6
|
+
|
|
7
|
+
Drop-in replacement for `Agent`. Returns predefined responses from a list, cycling when called more than once. Never calls an LLM.
|
|
8
|
+
|
|
9
|
+
```python
|
|
10
|
+
from tenxgraph.qa.testing import TestAgent
|
|
11
|
+
|
|
12
|
+
agent = TestAgent(
|
|
13
|
+
model="test-model", # compatibility only; ignored
|
|
14
|
+
responses=["Response 1", "Response 2"], # cycles on repeated calls
|
|
15
|
+
tools=["get_weather"], # optional; enables simulate_tool_calls
|
|
16
|
+
simulate_tool_calls=False, # True → first call returns tool-call message
|
|
17
|
+
)
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Swap into an existing graph:
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
test_agent = TestAgent(responses=["Mocked"])
|
|
24
|
+
graph.override_node("MAIN", test_agent)
|
|
25
|
+
app = graph.compile()
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Build a minimal graph inline:
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
from tenxgraph.core.graph import StateGraph
|
|
32
|
+
from tenxgraph.utils.constants import END
|
|
33
|
+
|
|
34
|
+
graph = StateGraph()
|
|
35
|
+
graph.add_node("MAIN", test_agent)
|
|
36
|
+
graph.set_entry_point("MAIN")
|
|
37
|
+
graph.add_edge("MAIN", END)
|
|
38
|
+
app = graph.compile()
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Tool-call simulation — set `simulate_tool_calls=True` (or pass `tools`). First invocation returns a `tools_calls` message; subsequent invocations return responses:
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
agent = TestAgent(responses=["Final answer."], tools=["get_weather"])
|
|
45
|
+
# call 1 → tool-call message requesting get_weather
|
|
46
|
+
# call 2 → "Final answer."
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Assertion helpers:
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
agent.assert_called() # called at least once
|
|
53
|
+
agent.assert_called_times(2) # called exactly n times
|
|
54
|
+
agent.assert_not_called()
|
|
55
|
+
agent.get_last_messages() # message list from last call
|
|
56
|
+
agent.get_last_tools() # tool specs from last call
|
|
57
|
+
agent.reset() # clear call_count and call_history
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Attributes: `call_count: int`, `call_history: list[dict]` (each has `messages`, `tools`, `kwargs`).
|
|
61
|
+
|
|
62
|
+
## QuickTest
|
|
63
|
+
|
|
64
|
+
One-liner async factory for common test scenarios. All methods are `async` — require `@pytest.mark.asyncio`.
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
from tenxgraph.qa.testing import QuickTest
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**single_turn** — single user/agent exchange:
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
result = await QuickTest.single_turn(
|
|
74
|
+
agent_response="Paris.",
|
|
75
|
+
user_message="Capital of France?",
|
|
76
|
+
config=None,
|
|
77
|
+
)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
**multi_turn** — multiple turns with accumulated history:
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
result = await QuickTest.multi_turn(
|
|
84
|
+
conversation=[
|
|
85
|
+
("Hello", "Hi there!"),
|
|
86
|
+
("What can you do?", "Many things."),
|
|
87
|
+
]
|
|
88
|
+
)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**with_tools** — agent that issues tool calls, then responds:
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
result = await QuickTest.with_tools(
|
|
95
|
+
query="Weather in London?",
|
|
96
|
+
response="It is sunny.",
|
|
97
|
+
tools=["get_weather"],
|
|
98
|
+
tool_responses={"get_weather": "22°C"},
|
|
99
|
+
)
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Builds a `MAIN → TOOL → MAIN → END` graph internally using `TestAgent(simulate_tool_calls=True)`.
|
|
103
|
+
|
|
104
|
+
**custom** — bring your own agent and optional graph setup:
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
result = await QuickTest.custom(
|
|
108
|
+
agent=my_test_agent,
|
|
109
|
+
user_message="Run it",
|
|
110
|
+
graph_setup=None, # callable(graph) → graph for extra wiring
|
|
111
|
+
)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## TestResult
|
|
115
|
+
|
|
116
|
+
Returned by every `QuickTest` method. All assertion methods return `self` for chaining.
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
result.assert_contains("Paris")
|
|
120
|
+
result.assert_not_contains("London")
|
|
121
|
+
result.assert_equals("Paris is the capital.")
|
|
122
|
+
result.assert_tool_called("get_weather")
|
|
123
|
+
result.assert_tool_called("get_weather", city="London") # with specific kwargs
|
|
124
|
+
result.assert_tool_not_called("send_email")
|
|
125
|
+
result.assert_message_count(3)
|
|
126
|
+
result.assert_no_errors()
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Attributes: `final_response: str`, `messages: list`, `tool_calls: list[dict]`, `state: dict`, `passed: bool`.
|
|
130
|
+
|
|
131
|
+
## MockToolRegistry
|
|
132
|
+
|
|
133
|
+
Registers sync or async mock tools and tracks every invocation. Pass `tools.get_tool_list()` to `ToolNode`.
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
from tenxgraph.qa.testing import MockToolRegistry
|
|
137
|
+
from tenxgraph.core.graph import ToolNode
|
|
138
|
+
|
|
139
|
+
tools = MockToolRegistry()
|
|
140
|
+
tools.register("get_weather", lambda city: f"22°C in {city}")
|
|
141
|
+
tools.register_async("search_web", async_fn)
|
|
142
|
+
|
|
143
|
+
tool_node = ToolNode(tools.get_tool_list())
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Call tracking:
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
tools.was_called("get_weather") # bool
|
|
150
|
+
tools.call_count("get_weather") # int
|
|
151
|
+
tools.get_calls("get_weather") # list[dict] with "args" and "kwargs"
|
|
152
|
+
tools.get_last_call("get_weather") # dict or None
|
|
153
|
+
|
|
154
|
+
tools.assert_called("get_weather")
|
|
155
|
+
tools.assert_called_with("get_weather", city="London")
|
|
156
|
+
tools.assert_call_count("get_weather", 2)
|
|
157
|
+
|
|
158
|
+
tools.reset() # clear history, keep functions
|
|
159
|
+
tools.clear() # clear history and functions
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## 10xgraph test CLI
|
|
163
|
+
|
|
164
|
+
Thin pytest wrapper. Reads defaults from `10xgraph.json → "test"` section; CLI flags override config.
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
10xgraph test # pytest auto-discovery from project root
|
|
168
|
+
10xgraph test tests/unit # target path
|
|
169
|
+
10xgraph test --coverage # adds --cov=. --cov-report=term-missing --cov-report=html:htmlcov
|
|
170
|
+
10xgraph test --coverage --html # opens htmlcov/index.html after run
|
|
171
|
+
10xgraph test -k "weather" # keyword filter forwarded to pytest
|
|
172
|
+
10xgraph test -- -m "not integration" --tb=short # raw pytest flags after --
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
`10xgraph.json` configuration:
|
|
176
|
+
|
|
177
|
+
```json
|
|
178
|
+
{
|
|
179
|
+
"test": {
|
|
180
|
+
"path": "tests",
|
|
181
|
+
"coverage": true,
|
|
182
|
+
"coverage_threshold": 80
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Exit code mirrors pytest: 0 = all pass, 1 = failures or coverage below threshold.
|
|
188
|
+
|
|
189
|
+
## Rules
|
|
190
|
+
|
|
191
|
+
- Never use real `Agent` in unit tests; always swap with `TestAgent`.
|
|
192
|
+
- All `QuickTest` methods are async — use `@pytest.mark.asyncio`.
|
|
193
|
+
- Call `agent.reset()` and `tools.reset()` between test cases to prevent state bleed.
|
|
194
|
+
- Mark slow or live-provider tests with `@pytest.mark.integration` so they are excluded from fast CI runs (`10xgraph test -- -m "not integration"`).
|
|
195
|
+
- `TestAgent(tools=[...])` auto-enables `simulate_tool_calls`; pair it with a real `ToolNode(tools.get_tool_list())` to exercise the full routing loop.
|
|
196
|
+
|
|
197
|
+
## Source Map
|
|
198
|
+
|
|
199
|
+
- TestAgent: https://github.com/10xGraph/10xGraph/blob/main/tenxgraph/qa/testing/test_agent.py
|
|
200
|
+
- QuickTest / TestResult: https://github.com/10xGraph/10xGraph/blob/main/tenxgraph/qa/testing/quick_test.py
|
|
201
|
+
- MockToolRegistry: https://github.com/10xGraph/10xGraph/blob/main/tenxgraph/qa/testing/mock_tools.py
|
|
202
|
+
- CLI test command: https://github.com/10xGraph/10xgraph-api/blob/main/tenxgraph_api/cli/commands/test.py
|