create-leafmesh 2.1.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.
- create_leafmesh/__init__.py +3 -0
- create_leafmesh/cli.py +252 -0
- create_leafmesh/create.py +106 -0
- create_leafmesh/templates/Dockerfile +21 -0
- create_leafmesh/templates/README.md +309 -0
- create_leafmesh/templates/agency/__init__.py +0 -0
- create_leafmesh/templates/agency/advisor_agent.py +151 -0
- create_leafmesh/templates/agency/external_agents.py +278 -0
- create_leafmesh/templates/agency/fallback_researcher_agent.py +80 -0
- create_leafmesh/templates/agency/greeter_agent.py +79 -0
- create_leafmesh/templates/agency/processor_agent.py +90 -0
- create_leafmesh/templates/agency/researcher_agent.py +99 -0
- create_leafmesh/templates/agency/scheduler_agent.py +67 -0
- create_leafmesh/templates/agency/tools.py +123 -0
- create_leafmesh/templates/claude_skills/leafmesh/SKILL.md +2049 -0
- create_leafmesh/templates/claude_skills/leafmesh/agent-config-fields.md +1309 -0
- create_leafmesh/templates/claude_skills/leafmesh/examples.md +537 -0
- create_leafmesh/templates/claude_skills/leafmesh/reference.md +492 -0
- create_leafmesh/templates/configs/config.yaml +1028 -0
- create_leafmesh/templates/docker-compose.yml +28 -0
- create_leafmesh/templates/dockerignore +17 -0
- create_leafmesh/templates/env +109 -0
- create_leafmesh/templates/gitignore +33 -0
- create_leafmesh/templates/hitl_stub_receiver.py +149 -0
- create_leafmesh/templates/main.py +105 -0
- create_leafmesh/templates/requirements.txt +10 -0
- create_leafmesh-2.1.0.dist-info/METADATA +6 -0
- create_leafmesh-2.1.0.dist-info/RECORD +31 -0
- create_leafmesh-2.1.0.dist-info/WHEEL +5 -0
- create_leafmesh-2.1.0.dist-info/entry_points.txt +2 -0
- create_leafmesh-2.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""LLM Agent — Auto-discovered with @chain_with_results (accumulating pipeline).
|
|
2
|
+
|
|
3
|
+
auto_discover matches function name 'researcher_agent' to YAML agent config.
|
|
4
|
+
|
|
5
|
+
Features showcased:
|
|
6
|
+
- @chain_with_results: each step's return is accumulated into a list
|
|
7
|
+
- Smart memory (BRD-009): strategy: "hybrid" with cross-session persistence
|
|
8
|
+
context["memory_posts"] has relevance+recency weighted past interactions
|
|
9
|
+
- tool_categories: ["data"] — all tools in the "data" category are available
|
|
10
|
+
- tools: ["math_eval", "timestamp"] — specific tools also included
|
|
11
|
+
- reasoning: true — enables chain-of-thought for deeper analysis
|
|
12
|
+
- allow_parallel_tool_calls: true — LLM can invoke multiple tools at once
|
|
13
|
+
|
|
14
|
+
Execution flow:
|
|
15
|
+
1. LLM generates response (with memory context + tools available)
|
|
16
|
+
2. This function post-processes the LLM response
|
|
17
|
+
3. @chain_with_results runs: analyze_data -> format_findings
|
|
18
|
+
Each step's result is accumulated in context["chain_results"]
|
|
19
|
+
"""
|
|
20
|
+
from leafmesh import chain_with_results, LeafMeshLogger
|
|
21
|
+
|
|
22
|
+
logger = LeafMeshLogger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def analyze_data(result, context):
|
|
26
|
+
"""Chain step 1: Enrich the result with analysis and a confidence score.
|
|
27
|
+
|
|
28
|
+
With @chain_with_results, this return value is appended to
|
|
29
|
+
context["chain_results"] AND passed to the next step.
|
|
30
|
+
"""
|
|
31
|
+
items = result.get("findings", [])
|
|
32
|
+
result["analysis"] = {
|
|
33
|
+
"total_findings": len(items),
|
|
34
|
+
"confidence_score": 0.85 if items else 0.1,
|
|
35
|
+
"data_quality": "high" if len(items) >= 3 else "limited",
|
|
36
|
+
}
|
|
37
|
+
return result
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def format_findings(result, context):
|
|
41
|
+
"""Chain step 2: Structure into a formal report format.
|
|
42
|
+
|
|
43
|
+
context["chain_results"] already contains the output from analyze_data.
|
|
44
|
+
"""
|
|
45
|
+
analysis = result.get("analysis", {})
|
|
46
|
+
result["report"] = {
|
|
47
|
+
"title": "Research Findings",
|
|
48
|
+
"confidence": analysis.get("confidence_score", 0),
|
|
49
|
+
"quality": analysis.get("data_quality", "unknown"),
|
|
50
|
+
"finding_count": analysis.get("total_findings", 0),
|
|
51
|
+
"formatted": True,
|
|
52
|
+
}
|
|
53
|
+
return result
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@chain_with_results(analyze_data, format_findings)
|
|
57
|
+
async def researcher_agent(llm_response, input_data, context):
|
|
58
|
+
"""Post-process the LLM's research response.
|
|
59
|
+
|
|
60
|
+
At this point:
|
|
61
|
+
- LLM already generated research using the YAML prompt + tools
|
|
62
|
+
- context["memory_posts"] has past conversation history (memory: true)
|
|
63
|
+
- After this function, @chain_with_results runs analyze_data -> format_findings
|
|
64
|
+
- Each step result is accumulated in context["chain_results"]
|
|
65
|
+
"""
|
|
66
|
+
# ─── Smart memory access (BRD-009: strategy "hybrid" in YAML) ───
|
|
67
|
+
# Past interactions are injected as context["memory_posts"], scored by
|
|
68
|
+
# relevance (0.6 weight) + recency (0.4 weight). Cross-session enabled
|
|
69
|
+
# means memory persists across sessions for returning users.
|
|
70
|
+
# Each post has "role" (user/assistant) and "content" (text).
|
|
71
|
+
memory_posts = context.get("memory_posts", [])
|
|
72
|
+
prior_findings_count = sum(1 for p in memory_posts if p.get("role") == "assistant")
|
|
73
|
+
|
|
74
|
+
# ─── Upstream yields access ───
|
|
75
|
+
# When processor_agent stores yields, the SDK injects them here.
|
|
76
|
+
upstream_yields = input_data.get("upstream_yields", {})
|
|
77
|
+
processor_yields = upstream_yields.get("processor_agent", {})
|
|
78
|
+
# processor_yields has: processed_items, item_count, status, alert
|
|
79
|
+
|
|
80
|
+
findings = []
|
|
81
|
+
if llm_response:
|
|
82
|
+
findings.append({"source": "llm", "content": llm_response})
|
|
83
|
+
|
|
84
|
+
calling_data = input_data.get("calling_agent_response", {})
|
|
85
|
+
if calling_data.get("processed_items"):
|
|
86
|
+
for item in calling_data["processed_items"]:
|
|
87
|
+
findings.append({"source": "upstream", "content": item.get("item", "")})
|
|
88
|
+
|
|
89
|
+
query = input_data.get("message", input_data.get("research_topic", ""))
|
|
90
|
+
logger.info(f"Research complete — {len(findings)} findings for: {query[:60]}")
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
"findings": findings,
|
|
94
|
+
"query": query,
|
|
95
|
+
"prior_research_sessions": prior_findings_count,
|
|
96
|
+
"upstream_item_count": processor_yields.get("item_count", 0),
|
|
97
|
+
"source_agent": "researcher_agent",
|
|
98
|
+
"status": "researched",
|
|
99
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""Programmatic Agent — Cron-scheduled, chains to advisor for analysis.
|
|
2
|
+
|
|
3
|
+
auto_discover matches function name 'scheduler_agent' to YAML agent config.
|
|
4
|
+
|
|
5
|
+
Features showcased:
|
|
6
|
+
- wake_up: "0 9 * * *" — cron expression triggers this agent automatically
|
|
7
|
+
- communication_type: "chain" — output chains to advisor_agent via can_call
|
|
8
|
+
- Entry point: mesh_call("scheduled_report", ...) for on-demand reports
|
|
9
|
+
- No LLM, no external deps — pure Python on a schedule
|
|
10
|
+
|
|
11
|
+
Use cases for scheduled agents:
|
|
12
|
+
- Daily/weekly report generation → chain to LLM agent for analysis
|
|
13
|
+
- Health checks and alerting → chain to human agent for review
|
|
14
|
+
- Periodic data aggregation → chain to downstream processors
|
|
15
|
+
|
|
16
|
+
All agent functions use the 3-param signature: (llm_response, input_data, context)
|
|
17
|
+
For programmatic agents, llm_response is always None.
|
|
18
|
+
input_data will be empty on scheduled runs (no upstream caller).
|
|
19
|
+
On-demand runs via mesh_call("scheduled_report", data) will have input_data.
|
|
20
|
+
"""
|
|
21
|
+
from datetime import datetime, timezone
|
|
22
|
+
from leafmesh import LeafMeshLogger
|
|
23
|
+
|
|
24
|
+
logger = LeafMeshLogger(__name__)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
async def scheduler_agent(llm_response, input_data, context):
|
|
28
|
+
"""Generate a status report with mesh metrics.
|
|
29
|
+
|
|
30
|
+
This runs on the cron schedule defined in config.yaml AND can be
|
|
31
|
+
triggered on-demand via mesh_call("scheduled_report", data).
|
|
32
|
+
|
|
33
|
+
communication_type: "chain" means the result is forwarded to
|
|
34
|
+
advisor_agent via can_call for LLM-powered analysis.
|
|
35
|
+
"""
|
|
36
|
+
now = datetime.now(timezone.utc)
|
|
37
|
+
trigger = input_data.get("trigger", "scheduled")
|
|
38
|
+
|
|
39
|
+
# ─── Gather mesh metrics ───
|
|
40
|
+
# In production, you'd pull real metrics from your data sources.
|
|
41
|
+
# The advisor_agent receives this as input_data and analyzes it.
|
|
42
|
+
checks = {
|
|
43
|
+
"system_status": "healthy",
|
|
44
|
+
"scheduled_run": trigger == "scheduled",
|
|
45
|
+
"on_demand": trigger != "scheduled",
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
metrics = {
|
|
49
|
+
"report_time": now.isoformat(),
|
|
50
|
+
"trigger_type": trigger,
|
|
51
|
+
"uptime_hours": 24,
|
|
52
|
+
"total_requests_today": 0,
|
|
53
|
+
"error_rate": 0.0,
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
logger.info(f"Report generated ({trigger}) — status={checks['system_status']}")
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
"report": f"Status report ({trigger}) at {now.isoformat()}",
|
|
60
|
+
"generated_at": now.isoformat(),
|
|
61
|
+
"checks": checks,
|
|
62
|
+
"metrics": metrics,
|
|
63
|
+
"processed_items": [
|
|
64
|
+
{"item": f"Daily {trigger} report", "category": "normal"},
|
|
65
|
+
],
|
|
66
|
+
"status": "report_generated",
|
|
67
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""Custom tools that LLM agents can call during generation.
|
|
2
|
+
|
|
3
|
+
Two decorator types:
|
|
4
|
+
@global_tool — Auto-registers in GlobalToolRegistry on import
|
|
5
|
+
@tool — Creates a local FunctionTool (register manually)
|
|
6
|
+
|
|
7
|
+
Tools are referenced in YAML agent config:
|
|
8
|
+
tools: ["word_count", "timestamp"] # specific tools by name
|
|
9
|
+
tool_categories: ["text", "utility"] # all tools in a category
|
|
10
|
+
tool_choice: "auto"
|
|
11
|
+
|
|
12
|
+
Import these in main.py so @global_tool registration runs:
|
|
13
|
+
from agency.tools import word_count, timestamp, format_as_markdown
|
|
14
|
+
"""
|
|
15
|
+
from leafmesh import global_tool, tool
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@global_tool(
|
|
19
|
+
name="word_count",
|
|
20
|
+
description="Count the number of words in a text string",
|
|
21
|
+
category="text",
|
|
22
|
+
)
|
|
23
|
+
def word_count(text: str) -> dict:
|
|
24
|
+
"""Count words in the given text."""
|
|
25
|
+
words = text.split()
|
|
26
|
+
return {"word_count": len(words), "character_count": len(text)}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@global_tool(
|
|
30
|
+
name="timestamp",
|
|
31
|
+
description="Get the current UTC timestamp",
|
|
32
|
+
category="utility",
|
|
33
|
+
)
|
|
34
|
+
def timestamp() -> dict:
|
|
35
|
+
"""Return the current UTC time."""
|
|
36
|
+
from datetime import datetime, timezone
|
|
37
|
+
now = datetime.now(timezone.utc)
|
|
38
|
+
return {"utc": now.isoformat(), "unix": int(now.timestamp())}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@global_tool(
|
|
42
|
+
name="math_eval",
|
|
43
|
+
description="Evaluate a simple math expression (addition, subtraction, multiplication, division)",
|
|
44
|
+
category="data",
|
|
45
|
+
)
|
|
46
|
+
def math_eval(expression: str) -> dict:
|
|
47
|
+
"""Safely evaluate a math expression."""
|
|
48
|
+
allowed = set("0123456789+-*/.(). ")
|
|
49
|
+
if not all(c in allowed for c in expression):
|
|
50
|
+
return {"error": "Invalid characters in expression"}
|
|
51
|
+
try:
|
|
52
|
+
result = eval(expression, {"__builtins__": {}}) # noqa: S307
|
|
53
|
+
return {"expression": expression, "result": result}
|
|
54
|
+
except Exception as e:
|
|
55
|
+
return {"error": str(e)}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@global_tool(
|
|
59
|
+
name="sensitive_data_lookup",
|
|
60
|
+
description="Look up sensitive data records (restricted access)",
|
|
61
|
+
category="data",
|
|
62
|
+
allowed_agents=["researcher_agent", "advisor_agent"],
|
|
63
|
+
requires_confirmation=True,
|
|
64
|
+
timeout_seconds=15,
|
|
65
|
+
)
|
|
66
|
+
def sensitive_data_lookup(record_id: str) -> dict:
|
|
67
|
+
"""Look up a sensitive data record by ID.
|
|
68
|
+
|
|
69
|
+
- allowed_agents: only researcher_agent and advisor_agent can call this
|
|
70
|
+
- requires_confirmation: manager must approve before execution
|
|
71
|
+
- timeout_seconds: auto-cancel if it takes longer than 15s
|
|
72
|
+
"""
|
|
73
|
+
return {
|
|
74
|
+
"record_id": record_id,
|
|
75
|
+
"data": f"Record {record_id} contents",
|
|
76
|
+
"classification": "internal",
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
# ─── @tool example (local, not auto-registered) ──────────────
|
|
81
|
+
# Use @tool for agent-specific tools that don't need global access.
|
|
82
|
+
|
|
83
|
+
@tool(
|
|
84
|
+
name="format_as_markdown",
|
|
85
|
+
description="Format a list of items as a markdown checklist",
|
|
86
|
+
timeout_seconds=10,
|
|
87
|
+
requires_confirmation=False,
|
|
88
|
+
)
|
|
89
|
+
def format_as_markdown(items: list) -> str:
|
|
90
|
+
"""Convert a list to markdown checklist."""
|
|
91
|
+
return "\n".join(f"- [ ] {item}" for item in items)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
# ═══════════════════════════════════════════════════════════════
|
|
95
|
+
# BUILT-IN TOOLS REFERENCE
|
|
96
|
+
# These are provided by LeafMesh — no code needed, just reference
|
|
97
|
+
# them by name in your YAML config under `tools:` or by category
|
|
98
|
+
# under `tool_categories:`.
|
|
99
|
+
#
|
|
100
|
+
# Category: "web"
|
|
101
|
+
# - web_search — Search the web (requires API key config)
|
|
102
|
+
# - web_scrape — Scrape a URL and return content
|
|
103
|
+
#
|
|
104
|
+
# Category: "data"
|
|
105
|
+
# - json_parse — Parse and validate JSON strings
|
|
106
|
+
# - csv_parse — Parse CSV data into structured format
|
|
107
|
+
#
|
|
108
|
+
# Category: "text"
|
|
109
|
+
# - text_summarize — Summarize long text passages
|
|
110
|
+
# - text_translate — Translate text between languages
|
|
111
|
+
#
|
|
112
|
+
# Category: "utility"
|
|
113
|
+
# - file_read — Read file contents
|
|
114
|
+
# - file_write — Write content to a file
|
|
115
|
+
# - http_request — Make HTTP requests
|
|
116
|
+
#
|
|
117
|
+
# YAML usage:
|
|
118
|
+
# tools: ["calculator", "web_search"] # pick specific tools
|
|
119
|
+
# tool_categories: ["data", "utility"] # pick entire categories
|
|
120
|
+
# tool_choice: "auto" # auto | none | required
|
|
121
|
+
# allow_parallel_tool_calls: true # LLM can call multiple tools at once
|
|
122
|
+
# max_tool_calls_per_message: 10 # safety limit per LLM turn
|
|
123
|
+
# ═══════════════════════════════════════════════════════════════
|