programgarden 1.23.1__tar.gz → 1.24.1__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.
Files changed (36) hide show
  1. {programgarden-1.23.1 → programgarden-1.24.1}/PKG-INFO +4 -4
  2. {programgarden-1.23.1 → programgarden-1.24.1}/programgarden/__init__.py +21 -0
  3. {programgarden-1.23.1 → programgarden-1.24.1}/programgarden/client.py +12 -1
  4. {programgarden-1.23.1 → programgarden-1.24.1}/programgarden/deep_fixtures.py +71 -0
  5. {programgarden-1.23.1 → programgarden-1.24.1}/programgarden/executor.py +532 -29
  6. {programgarden-1.23.1 → programgarden-1.24.1}/programgarden/resolver.py +164 -27
  7. programgarden-1.24.1/programgarden/semantic_rules.py +404 -0
  8. {programgarden-1.23.1 → programgarden-1.24.1}/pyproject.toml +4 -4
  9. {programgarden-1.23.1 → programgarden-1.24.1}/README.md +0 -0
  10. {programgarden-1.23.1 → programgarden-1.24.1}/programgarden/binding_validator.py +0 -0
  11. {programgarden-1.23.1 → programgarden-1.24.1}/programgarden/context.py +0 -0
  12. {programgarden-1.23.1 → programgarden-1.24.1}/programgarden/database/__init__.py +0 -0
  13. {programgarden-1.23.1 → programgarden-1.24.1}/programgarden/database/checkpoint_manager.py +0 -0
  14. {programgarden-1.23.1 → programgarden-1.24.1}/programgarden/database/query_builder.py +0 -0
  15. {programgarden-1.23.1 → programgarden-1.24.1}/programgarden/database/workflow_position_tracker.py +0 -0
  16. {programgarden-1.23.1 → programgarden-1.24.1}/programgarden/database/workflow_risk_tracker.py +0 -0
  17. {programgarden-1.23.1 → programgarden-1.24.1}/programgarden/node_runner.py +0 -0
  18. {programgarden-1.23.1 → programgarden-1.24.1}/programgarden/plugin/__init__.py +0 -0
  19. {programgarden-1.23.1 → programgarden-1.24.1}/programgarden/plugin/sandbox.py +0 -0
  20. {programgarden-1.23.1 → programgarden-1.24.1}/programgarden/providers/__init__.py +0 -0
  21. {programgarden-1.23.1 → programgarden-1.24.1}/programgarden/providers/llm_errors.py +0 -0
  22. {programgarden-1.23.1 → programgarden-1.24.1}/programgarden/providers/llm_provider.py +0 -0
  23. {programgarden-1.23.1 → programgarden-1.24.1}/programgarden/reconnect_handler.py +0 -0
  24. {programgarden-1.23.1 → programgarden-1.24.1}/programgarden/resource/__init__.py +0 -0
  25. {programgarden-1.23.1 → programgarden-1.24.1}/programgarden/resource/context.py +0 -0
  26. {programgarden-1.23.1 → programgarden-1.24.1}/programgarden/resource/limiter.py +0 -0
  27. {programgarden-1.23.1 → programgarden-1.24.1}/programgarden/resource/monitor.py +0 -0
  28. {programgarden-1.23.1 → programgarden-1.24.1}/programgarden/resource/throttle.py +0 -0
  29. {programgarden-1.23.1 → programgarden-1.24.1}/programgarden/tools/__init__.py +0 -0
  30. {programgarden-1.23.1 → programgarden-1.24.1}/programgarden/tools/credential_tools.py +0 -0
  31. {programgarden-1.23.1 → programgarden-1.24.1}/programgarden/tools/definition_tools.py +0 -0
  32. {programgarden-1.23.1 → programgarden-1.24.1}/programgarden/tools/event_tools.py +0 -0
  33. {programgarden-1.23.1 → programgarden-1.24.1}/programgarden/tools/job_tools.py +0 -0
  34. {programgarden-1.23.1 → programgarden-1.24.1}/programgarden/tools/registry_tools.py +0 -0
  35. {programgarden-1.23.1 → programgarden-1.24.1}/programgarden/tools/sqlite_tools.py +0 -0
  36. {programgarden-1.23.1 → programgarden-1.24.1}/programgarden/validation_recommender.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: programgarden
3
- Version: 1.23.1
3
+ Version: 1.24.1
4
4
  Summary: ProgramGarden - 노드 기반 자동매매 DSL 실행 엔진
5
5
  Author: 프로그램동산
6
6
  Author-email: coding@programgarden.com
@@ -14,9 +14,9 @@ Requires-Dist: aiosqlite (>=0.20.0,<0.21.0)
14
14
  Requires-Dist: croniter (>=6.0.0,<7.0.0)
15
15
  Requires-Dist: litellm (>=1.40.0)
16
16
  Requires-Dist: lxml (>=6.0.2,<7.0.0)
17
- Requires-Dist: programgarden-community (>=1.13.8,<2.0.0)
18
- Requires-Dist: programgarden-core (>=1.14.5,<2.0.0)
19
- Requires-Dist: programgarden-finance (>=1.6.10,<2.0.0)
17
+ Requires-Dist: programgarden-community (>=1.13.9,<2.0.0)
18
+ Requires-Dist: programgarden-core (>=1.15.1,<2.0.0)
19
+ Requires-Dist: programgarden-finance (>=1.6.11,<2.0.0)
20
20
  Requires-Dist: psutil (>=6.0.0,<7.0.0)
21
21
  Requires-Dist: psycopg2-binary (>=2.9.11,<3.0.0)
22
22
  Requires-Dist: pydantic (>=2.0.0,<3.0.0)
@@ -14,6 +14,17 @@ from programgarden.executor import WorkflowExecutor
14
14
  from programgarden.context import ExecutionContext
15
15
  from programgarden.client import ProgramGarden
16
16
  from programgarden.node_runner import NodeRunner
17
+ from programgarden.semantic_rules import (
18
+ analyze_workflow_semantics,
19
+ normalize_severities,
20
+ DEFAULT_SEMANTIC_SEVERITIES,
21
+ STRICT_SEMANTIC_SEVERITIES,
22
+ ALL_RULES,
23
+ RULE_ORDER_QTY_FROM_AI,
24
+ RULE_STRUCTURED_OUTPUT_NO_SCHEMA,
25
+ RULE_HARDCODED_ORDER_QTY,
26
+ RULE_ORDER_IGNORED_FIELD,
27
+ )
17
28
  from programgarden_core.bases.listener import (
18
29
  NodeState,
19
30
  EdgeState,
@@ -140,4 +151,14 @@ __all__ = [
140
151
  "get_events",
141
152
  "get_job_summary",
142
153
  "analyze_performance",
154
+ # Semantic / safety layer (deep_validate R1~R4)
155
+ "analyze_workflow_semantics",
156
+ "normalize_severities",
157
+ "DEFAULT_SEMANTIC_SEVERITIES",
158
+ "STRICT_SEMANTIC_SEVERITIES",
159
+ "ALL_RULES",
160
+ "RULE_ORDER_QTY_FROM_AI",
161
+ "RULE_STRUCTURED_OUTPUT_NO_SCHEMA",
162
+ "RULE_HARDCODED_ORDER_QTY",
163
+ "RULE_ORDER_IGNORED_FIELD",
143
164
  ]
@@ -80,6 +80,7 @@ class ProgramGarden:
80
80
  *,
81
81
  fixtures: Optional[Dict[str, Any]] = None,
82
82
  timeout: float = 15.0,
83
+ semantic_rules: Optional[Dict[str, Any]] = None,
83
84
  ) -> ValidationResult:
84
85
  """Deep-validate a workflow via virtual full-execution (never raises).
85
86
 
@@ -96,6 +97,11 @@ class ProgramGarden:
96
97
  fixtures: Optional per-node fixture overrides, keyed by node id or
97
98
  node type (merged shallowly on top of the default fixture).
98
99
  timeout: Hard timeout (seconds) for the single validation pass.
100
+ semantic_rules: Optional per-rule severity config for the configurable
101
+ semantic/safety layer (R1~R4). ``None`` (default) skips the layer;
102
+ pass ``programgarden.semantic_rules.STRICT_SEMANTIC_SEVERITIES`` (or
103
+ a ``{rule_id: "error"|"warning"|"off"}`` dict) to opt in. See
104
+ ``WorkflowExecutor.deep_validate`` for details.
99
105
 
100
106
  Returns:
101
107
  ValidationResult — ``errors`` carry structured per-node ErrorInfo;
@@ -109,7 +115,12 @@ class ProgramGarden:
109
115
  ... print(err.short())
110
116
  """
111
117
  return _run_coro_sync(
112
- self.executor.deep_validate(definition, fixtures=fixtures, timeout=timeout)
118
+ self.executor.deep_validate(
119
+ definition,
120
+ fixtures=fixtures,
121
+ timeout=timeout,
122
+ semantic_rules=semantic_rules,
123
+ )
113
124
  )
114
125
 
115
126
  def run(
@@ -361,6 +361,77 @@ def broker_connection_fixture(
361
361
  }
362
362
 
363
363
 
364
+ def _schema_default_value(schema: Any) -> Any:
365
+ """Best-effort default value for one ``output_schema`` entry.
366
+
367
+ An entry is either a bare type string (``"number"``) or a JSON-schema-ish
368
+ dict (``{"type": "...", "enum": [...], "items": {...}, "properties": {...}}``)
369
+ — the two shapes ``AIAgentNodeExecutor._build_output_instruction`` /
370
+ ``_validate_structured`` already accept. Returns a type-correct placeholder
371
+ so a downstream ``{{ nodes.<agent>.response.<field> }}`` binding resolves to a
372
+ real value (an enum field resolves to its first allowed value, an object to
373
+ its declared properties, an array to a single shaped element).
374
+ """
375
+ if isinstance(schema, str):
376
+ type_name, spec = schema, {}
377
+ elif isinstance(schema, dict):
378
+ type_name, spec = str(schema.get("type", "string")), schema
379
+ else:
380
+ return "deep_validate"
381
+
382
+ enum_vals = spec.get("enum") if isinstance(spec, dict) else None
383
+ if enum_vals:
384
+ return enum_vals[0]
385
+
386
+ if type_name in ("string", "str"):
387
+ return "deep_validate"
388
+ if type_name in ("number", "float"):
389
+ return 1.0
390
+ if type_name in ("integer", "int"):
391
+ return 1
392
+ if type_name in ("boolean", "bool"):
393
+ return True
394
+ if type_name == "array":
395
+ items = spec.get("items") if isinstance(spec, dict) else None
396
+ if isinstance(items, (dict, str)):
397
+ return [_schema_default_value(items)]
398
+ return []
399
+ if type_name in ("object", "dict"):
400
+ props = spec.get("properties") if isinstance(spec, dict) else None
401
+ if isinstance(props, dict):
402
+ return {key: _schema_default_value(sub) for key, sub in props.items()}
403
+ return {}
404
+ return "deep_validate"
405
+
406
+
407
+ def ai_agent_fixture(config: Dict[str, Any]) -> Dict[str, Any]:
408
+ """AIAgentNode deep fixture — schema-shaped ``{"response": ...}``, no LLM call.
409
+
410
+ A deep run must never hit a live LLM (cost, non-determinism, network) nor
411
+ silently swallow a failed model call. This builds the ``response`` output
412
+ port directly from the node's declared ``output_format`` / ``output_schema``
413
+ so downstream consumers see the same shape they would at runtime:
414
+
415
+ - ``"structured"`` + ``output_schema`` → a dict with every declared field
416
+ populated by a type-correct placeholder (enum → first value, nested
417
+ object/array shaped recursively). This is what makes
418
+ ``{{ nodes.<agent>.response.<field> }}`` bindings resolve in deep mode.
419
+ - ``"json"`` → an empty dict (no schema to shape it).
420
+ - ``"text"`` / anything else → a placeholder string.
421
+ """
422
+ output_format = config.get("output_format", "text")
423
+ output_schema = config.get("output_schema")
424
+ if output_format == "structured" and isinstance(output_schema, dict) and output_schema:
425
+ response: Any = {
426
+ key: _schema_default_value(spec) for key, spec in output_schema.items()
427
+ }
428
+ elif output_format == "json":
429
+ response = {}
430
+ else: # "text" or unknown → raw string
431
+ response = "deep_validate: AIAgentNode response (virtual run, no live LLM call)"
432
+ return {"response": response}
433
+
434
+
364
435
  def apply_override(default: Dict[str, Any], override: Optional[Dict[str, Any]]) -> Dict[str, Any]:
365
436
  """Shallow-merge a caller override on top of a default fixture.
366
437