Skip to content

Commit 1e29e5d

Browse files
authored
Merge branch 'XSpoonAi:main' into main
2 parents 373cdd4 + b4f9733 commit 1e29e5d

File tree

4 files changed

+396
-44
lines changed

4 files changed

+396
-44
lines changed

docs/graph-system/advanced-features.md

Lines changed: 89 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -285,10 +285,11 @@ if __name__ == "__main__":
285285
Let an LLM decide the next step:
286286

287287
```python
288+
import asyncio
288289
from spoon_ai.graph.config import GraphConfig, RouterConfig
289-
from typing import TypedDict
290+
from typing import TypedDict, Any
290291

291-
from spoon_ai.graph import StateGraph
292+
from spoon_ai.graph import StateGraph, END
292293

293294
config = GraphConfig(
294295
router=RouterConfig(
@@ -303,15 +304,53 @@ class AnalysisState(TypedDict, total=False):
303304
user_query: str
304305
output: str
305306

306-
graph = StateGraph(AnalysisState)
307+
async def route(state: AnalysisState) -> dict:
308+
return {}
309+
310+
async def price_handler(state: AnalysisState) -> dict:
311+
return {"output": "Price handler executed"}
312+
313+
async def trade_handler(state: AnalysisState) -> dict:
314+
return {"output": "Trade handler executed"}
315+
316+
async def analysis_handler(state: AnalysisState) -> dict:
317+
return {"output": "Analysis handler executed"}
318+
319+
async def fallback_handler(state: AnalysisState) -> dict:
320+
return {"output": "Fallback handler executed"}
321+
322+
graph = StateGraph[Any](AnalysisState)
323+
graph.add_node("route", route)
324+
graph.add_node("price_handler", price_handler)
325+
graph.add_node("trade_handler", trade_handler)
326+
graph.add_node("analysis_handler", analysis_handler)
327+
graph.add_node("fallback_handler", fallback_handler)
328+
graph.set_entry_point("route")
329+
330+
graph.add_edge("price_handler", END)
331+
graph.add_edge("trade_handler", END)
332+
graph.add_edge("analysis_handler", END)
333+
graph.add_edge("fallback_handler", END)
334+
307335
graph.config = config
308336

309-
# Or enable after creation
337+
# Enable LLM routing
310338
graph.enable_llm_routing(config={
311339
"model": "gpt-4",
312340
"temperature": 0.1,
313341
"max_tokens": 50,
314342
})
343+
344+
app = graph.compile()
345+
346+
347+
async def main():
348+
result = await app.invoke({"user_query": "What is the price of Bitcoin?", "output": ""})
349+
print(result["output"])
350+
351+
352+
if __name__ == "__main__":
353+
asyncio.run(main())
315354
```
316355

317356
### Routing Decision Matrix
@@ -384,6 +423,20 @@ graph.add_edge("fetch_kraken", "aggregate")
384423
graph.add_edge("aggregate", END)
385424

386425
app = graph.compile()
426+
427+
async def main():
428+
result = await app.invoke({
429+
"symbol": "BTC",
430+
"binance": {},
431+
"coinbase": {},
432+
"kraken": {},
433+
"output": ""
434+
})
435+
print(result["output"])
436+
437+
if __name__ == "__main__":
438+
import asyncio
439+
asyncio.run(main())
387440
```
388441

389442
### Join Strategies
@@ -518,6 +571,38 @@ graph.add_edge("aggregate", END)
518571

519572
graph.set_entry_point("fetch_a")
520573
app = graph.compile()
574+
575+
576+
async def main():
577+
# Test case 1: Normal execution
578+
print("Test 1: Normal execution (quorum: 2 of 3)")
579+
result1 = await app.invoke({
580+
"symbol": "BTC",
581+
"source_a_data": {},
582+
"source_b_data": {},
583+
"source_c_data": {},
584+
"aggregated_price": 0.0,
585+
"errors": []
586+
})
587+
print(f" Aggregated price: ${result1['aggregated_price']:.2f}")
588+
print(f" Errors: {result1.get('errors', [])}")
589+
590+
# Test case 2: One source fails (quorum still met)
591+
print("\nTest 2: Source C fails (quorum: 2 of 3 still met)")
592+
result2 = await app.invoke({
593+
"symbol": "FAIL",
594+
"source_a_data": {},
595+
"source_b_data": {},
596+
"source_c_data": {},
597+
"aggregated_price": 0.0,
598+
"errors": []
599+
})
600+
print(f" Aggregated price: ${result2['aggregated_price']:.2f}")
601+
print(f" Errors: {result2.get('errors', [])}")
602+
603+
604+
if __name__ == "__main__":
605+
asyncio.run(main())
521606
```
522607

523608
---

docs/graph-system/building-graphs.md

Lines changed: 56 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,28 @@ graph.add_edge("news", END)
149149
graph.add_edge("general", END)
150150

151151
app = graph.compile()
152+
153+
async def main():
154+
# Test with different queries
155+
test_queries = [
156+
"What is the price of Bitcoin?",
157+
"Show me crypto news",
158+
"Tell me about blockchain"
159+
]
160+
161+
for query in test_queries:
162+
result = await app.invoke({
163+
"query": query,
164+
"category": "",
165+
"result": ""
166+
})
167+
print(f"Query: {query}")
168+
print(f"Category: {result['category']}")
169+
print(f"Result: {result['result']}")
170+
171+
if __name__ == "__main__":
172+
import asyncio
173+
asyncio.run(main())
152174
```
153175

154176
### API Reference
@@ -341,6 +363,29 @@ template = GraphTemplate(
341363
builder = DeclarativeGraphBuilder(DataState)
342364
graph = builder.build(template)
343365
app = graph.compile()
366+
367+
368+
async def main():
369+
# Test the parallel group execution
370+
result = await app.invoke({
371+
"symbol": "BTC",
372+
"binance_data": {},
373+
"coinbase_data": {},
374+
"kraken_data": {},
375+
"aggregated": {}
376+
})
377+
378+
print("Parallel Group Execution Results:")
379+
print(f"Symbol: {result['symbol']}")
380+
print(f"\nBinance Data: {result['binance_data']}")
381+
print(f"Coinbase Data: {result['coinbase_data']}")
382+
print(f"Kraken Data: {result['kraken_data']}")
383+
print(f"\nAggregated Average Price: {result['aggregated']['average_price']}")
384+
385+
386+
if __name__ == "__main__":
387+
import asyncio
388+
asyncio.run(main())
344389
```
345390

346391
### Template Serialization
@@ -544,7 +589,7 @@ from typing import TypedDict
544589

545590
from spoon_ai.graph import StateGraph, END
546591
from spoon_ai.graph.builder import GraphTemplate, NodeSpec, EdgeSpec
547-
592+
from spoon_ai.graph.builder import DeclarativeGraphBuilder
548593

549594
class MyState(TypedDict, total=False):
550595
input: str
@@ -565,6 +610,11 @@ template = GraphTemplate(
565610
nodes=[NodeSpec("process", process_fn)],
566611
edges=[EdgeSpec("process", END)],
567612
)
613+
614+
# Build declarative graph
615+
builder = DeclarativeGraphBuilder(MyState)
616+
declarative_graph = builder.build(template)
617+
declarative_app = declarative_graph.compile()
568618
```
569619

570620
### 2. Use Meaningful Node Names
@@ -597,11 +647,6 @@ graph = StateGraph(MyState)
597647
graph.add_node("classify_user_intent", classify_fn)
598648
graph.add_node("fetch_market_data", fetch_fn)
599649
graph.add_node("generate_recommendation", recommend_fn)
600-
601-
# Bad: Generic names
602-
graph.add_node("step1", classify_fn)
603-
graph.add_node("step2", fetch_fn)
604-
graph.add_node("step3", recommend_fn)
605650
```
606651

607652
### 3. Group Related Functionality
@@ -686,7 +731,7 @@ from typing import TypedDict
686731
from spoon_ai.graph import END
687732
from spoon_ai.graph.builder import GraphTemplate, NodeSpec, EdgeSpec
688733
from spoon_ai.graph.config import GraphConfig
689-
734+
from spoon_ai.graph.builder import DeclarativeGraphBuilder
690735

691736
class MyState(TypedDict, total=False):
692737
input: str
@@ -711,6 +756,10 @@ template = GraphTemplate(
711756
# and market analysis with LLM-powered routing
712757
),
713758
)
759+
# Build and compile
760+
builder = DeclarativeGraphBuilder(MyState)
761+
graph = builder.build(template)
762+
app = graph.compile()
714763
```
715764

716765
---

docs/graph-system/integration.md

Lines changed: 53 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -374,11 +374,24 @@ async def mcp_search_node(state: SearchState) -> dict:
374374

375375
result = await tavily_tool.execute(query=state.get("query", ""), max_results=5)
376376
return {"search_results": result}
377+
378+
async def main():
379+
# Test the MCP tool node
380+
result = await mcp_search_node({"query": "Bitcoin price"})
381+
print(f"\nQuery: Bitcoin price")
382+
print(f"Results: {result.get('search_results', 'No results')}")
383+
384+
385+
if __name__ == "__main__":
386+
import asyncio
387+
asyncio.run(main())
377388
```
378389

379390
### High-Level MCP Integration
380391

381392
```python
393+
import os
394+
import asyncio
382395
from typing import TypedDict
383396

384397
from spoon_ai.graph.builder import HighLevelGraphAPI
@@ -389,19 +402,39 @@ class MyState(TypedDict, total=False):
389402
user_query: str
390403

391404

392-
api = HighLevelGraphAPI(MyState)
405+
async def main():
406+
api = HighLevelGraphAPI(MyState)
407+
408+
# Check if TAVILY_API_KEY is configured
409+
tavily_key = os.getenv("TAVILY_API_KEY", "").strip()
410+
if not tavily_key or "..." in tavily_key:
411+
print("Note: TAVILY_API_KEY not configured. MCP tool registration skipped.")
412+
print("To use this example, set TAVILY_API_KEY environment variable.")
413+
return
414+
415+
# Register MCP tool using HighLevelGraphAPI
416+
api.register_mcp_tool(
417+
intent_category="research",
418+
spec=MCPToolSpec(name="tavily-search"),
419+
config={
420+
"command": "npx",
421+
"args": ["--yes", "tavily-mcp"],
422+
"env": {"TAVILY_API_KEY": tavily_key},
423+
},
424+
)
425+
426+
# Create the tool instance
427+
tool = api.create_mcp_tool("tavily-search")
428+
429+
if tool:
430+
result = await tool.execute(query="Bitcoin price", max_results=3)
431+
print(f"Search results: {len(str(result))} characters returned")
432+
else:
433+
print("Failed to create MCP tool. Check configuration.")
393434

394-
api.register_mcp_tool(
395-
intent_category="research",
396-
spec=MCPToolSpec(name="tavily-search"),
397-
config={
398-
"command": "npx",
399-
"args": ["--yes", "tavily-mcp"],
400-
"env": {"TAVILY_API_KEY": "..."},
401-
},
402-
)
403435

404-
tool = api.create_mcp_tool("tavily-search")
436+
if __name__ == "__main__":
437+
asyncio.run(main())
405438
```
406439

407440
### For More MCP Information
@@ -632,6 +665,15 @@ graph.add_edge("process", END)
632665

633666
graph.enable_monitoring(["execution_time", "success_rate", "routing_performance", "node_stats"])
634667
app = graph.compile()
668+
669+
async def main():
670+
# Execute the graph
671+
result = await app.invoke({"input": "test", "output": ""})
672+
print(result)
673+
674+
if __name__ == "__main__":
675+
import asyncio
676+
asyncio.run(main())
635677
```
636678

637679
### Execution Metrics

0 commit comments

Comments
 (0)