Explorar el Código

feat: CEK machine ClaudeLam support, trace format compat, CLI refactor

- CEK machine: support ClaudeLam (Claude Code CLI, no API key) via duck typing
- CLI trace: handle both v1 and v2 trace formats gracefully
- launch_paas: extract _apply_overrides helper
- Add .github/ CI config and .gitignore

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
kenny67nju hace 5 meses
padre
commit
f87303b549
Se han modificado 5 ficheros con 250 adiciones y 20 borrados
  1. 40 0
      .github/workflows/ci.yml
  2. 166 0
      .gitignore
  3. 11 7
      agentexample/agentbuilder67/launch_paas.py
  4. 10 2
      lambdagent/cek_machine.py
  5. 23 11
      lambdagent/cli/main.py

+ 40 - 0
.github/workflows/ci.yml

@@ -0,0 +1,40 @@
+name: CI
+
+on:
+  push:
+    branches: [main]
+  pull_request:
+    branches: [main]
+
+jobs:
+  test:
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        python-version: ["3.10", "3.11", "3.12"]
+    steps:
+      - uses: actions/checkout@v4
+      - name: Set up Python
+        uses: actions/setup-python@v5
+        with:
+          python-version: ${{ matrix.python-version }}
+      - name: Install dependencies
+        run: |
+          pip install -e ".[dev]" 2>/dev/null || pip install pyyaml fastapi uvicorn pytest
+      - name: Run tests
+        run: python -m pytest tests/ -v --tb=short
+      - name: Lint check
+        run: python -c "from lambdagent.fromconfig import lint_config; print('Lint module OK')"
+
+  lint:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+      - uses: actions/setup-python@v5
+        with:
+          python-version: "3.12"
+      - run: pip install pyyaml
+      - name: Check imports
+        run: |
+          python -c "import lambdagent; print(f'lambdagent OK: {len(lambdagent.__all__)} exports')"
+          python -c "from agentpaas.api.app import app; print(f'AgentPaaS OK: {len([r for r in app.routes if hasattr(r, \"methods\")])} routes')"

+ 166 - 0
.gitignore

@@ -0,0 +1,166 @@
+# ==============================
+# Python bytecode
+# ==============================
+__pycache__/
+*.py[cod]
+*$py.class
+
+# ==============================
+# C extensions
+# ==============================
+*.so
+
+# ==============================
+# Distribution / packaging
+# ==============================
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+*.egg-info/
+*.egg
+MANIFEST
+
+# ==============================
+# Virtual environments
+# ==============================
+venv/
+.env/
+.venv/
+ENV/
+env/
+env.bak/
+venv.bak/
+
+# ==============================
+# Installer logs
+# ==============================
+pip-log.txt
+pip-delete-this-directory.txt
+
+# ==============================
+# Unit test / coverage
+# ==============================
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+pytest_cache/
+nosetests.xml
+coverage.xml
+*.cover
+*.py,cover
+
+# ==============================
+# Jupyter Notebook
+# ==============================
+.ipynb_checkpoints
+
+# ==============================
+# PyInstaller
+# ==============================
+*.manifest
+*.spec
+
+# ==============================
+# Logs
+# ==============================
+*.log
+
+# ==============================
+# OS files
+# ==============================
+.DS_Store
+Thumbs.db
+
+# ==============================
+# Database files
+# ==============================
+*.db
+*.sqlite
+*.sqlite3
+
+# ==============================
+# IDE
+# ==============================
+.vscode/
+.idea/
+*.swp
+*.swo
+.claude/
+
+# ==============================
+# mypy
+# ==============================
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# ==============================
+# Pyre
+# ==============================
+.pyre/
+
+# ==============================
+# Ruff / lint
+# ==============================
+.ruff_cache/
+
+# ==============================
+# Pylint
+# ==============================
+.pylint.d/
+
+# ==============================
+# pytype
+# ==============================
+.pytype/
+
+# ==============================
+# dotenv
+# ==============================
+.env
+.env.*
+
+# ==============================
+# Local data / experiment output
+# ==============================
+data/
+outputs/
+checkpoints/
+models/
+
+# ==============================
+# LaTeX build artifacts
+# ==============================
+paper/*.aux
+paper/*.bbl
+paper/*.blg
+paper/*.log
+paper/*.out
+paper/*.fls
+paper/*.fdb_latexmk
+paper/*.synctex.gz
+
+# ==============================
+# Large feature / experiment cache (keep local only)
+# ==============================
+results/p0_features/
+results/*.npz
+__pycache__/
+patents/patent-01-lint/node_modules/
+
+# ==============================
+# Agent workspace (local data, not tracked)
+# ==============================
+agentexample/**/workspace/

+ 11 - 7
agentexample/agentbuilder67/launch_paas.py

@@ -700,6 +700,16 @@ def cmd_all(args):
 # CLI Entry Point
 # ═══════════════════════════════════════════════════════════
 
+def _apply_overrides(args):
+    global MAX_REVISION_ROUNDS, MAX_TURNS_PER_PHASE, PHASE_TIMEOUT
+    if hasattr(args, "max_rounds") and args.max_rounds:
+        MAX_REVISION_ROUNDS = args.max_rounds
+    if hasattr(args, "max_turns") and args.max_turns:
+        MAX_TURNS_PER_PHASE = args.max_turns
+    if hasattr(args, "timeout") and args.timeout:
+        PHASE_TIMEOUT = args.timeout
+
+
 def main():
     parser = argparse.ArgumentParser(
         description="AgentBuilder — Multi-agent Configuration Generator",
@@ -751,13 +761,7 @@ Examples:
     args = parser.parse_args()
 
     # 应用参数覆盖
-    global MAX_REVISION_ROUNDS, MAX_TURNS_PER_PHASE, PHASE_TIMEOUT
-    if hasattr(args, "max_rounds") and args.max_rounds:
-        MAX_REVISION_ROUNDS = args.max_rounds
-    if hasattr(args, "max_turns") and args.max_turns:
-        MAX_TURNS_PER_PHASE = args.max_turns
-    if hasattr(args, "timeout") and args.timeout:
-        PHASE_TIMEOUT = args.timeout
+    _apply_overrides(args)
 
     commands = {
         "serve": cmd_serve,

+ 10 - 2
lambdagent/cek_machine.py

@@ -364,7 +364,8 @@ class AgentCEKMachine:
         s = self.state
 
         # ── C-Lam: YIELD to LLM oracle ──
-        if isinstance(op, Lam):
+        # Supports both Lam (API key) and ClaudeLam (Claude Code CLI, no API key)
+        if isinstance(op, Lam) or _is_claude_lam(op):
             t0 = time.time()
             ctx = Context(bindings=s.env, memory=s.store)
             result = op.apply(arg, ctx)
@@ -372,10 +373,11 @@ class AgentCEKMachine:
 
             # Extract cost from context trace
             tokens = ctx.trace[-1].tokens_used if ctx.trace else 0
+            model_name = getattr(op, 'model', 'claude-code')
             cost_llm = CostVector(
                 tokens=tokens,
                 latency=elapsed,
-                money=tokens * _price_per_token(op.model),
+                money=tokens * _price_per_token(model_name),
             )
             label = Label(LabelKind.LLM, op._name, arg, result)
             s.control = result
@@ -623,6 +625,12 @@ def _term_repr(t) -> str:
     return repr(t)[:40]
 
 
+def _is_claude_lam(op) -> bool:
+    """Check if op is a ClaudeLam instance (without importing it directly,
+    to avoid hard dependency on agentexample)."""
+    return type(op).__name__ == 'ClaudeLam'
+
+
 def _price_per_token(model: str) -> float:
     """Rough per-token price for cost tracking."""
     prices = {

+ 23 - 11
lambdagent/cli/main.py

@@ -499,6 +499,17 @@ def cmd_lint_inner(config_path: str, level: str, fmt: str) -> int:
 # trace
 # ════════════════════════════════════════════════════════════
 
+def _trace_get(e, key, default=None):
+    """兼容两种 trace 格式(v1: term/duration_ms, v2: term_type+name/elapsed_ms)。"""
+    if key == "term":
+        return e.get("term") or f"{e.get('term_type', '')}:{e.get('name', '')}"
+    if key == "duration_ms":
+        return e.get("duration_ms") or e.get("elapsed_ms", 0)
+    if key == "tokens":
+        return e.get("tokens") or (e.get("tokens_in", 0) + e.get("tokens_out", 0))
+    return e.get(key, default)
+
+
 def cmd_trace(args) -> int:
     """查看 β-规约追踪"""
     if not os.path.exists(args.file):
@@ -511,12 +522,12 @@ def cmd_trace(args) -> int:
     if args.step is not None:
         if 0 <= args.step < len(trace):
             e = trace[args.step]
-            print(f"β[{e['step']}] {e['term']}")
-            print(f"  Duration: {e['duration_ms']}ms")
+            print(f"β[{e.get('step', args.step)}] {_trace_get(e, 'term')}")
+            print(f"  Duration: {_trace_get(e, 'duration_ms'):.0f}ms")
             print(f"  Model:    {e.get('model', 'N/A')}")
-            print(f"  Tokens:   {e.get('tokens', 'N/A')}")
-            print(f"  Input:    {e['input'][:300]}")
-            print(f"  Output:   {e['output'][:300]}")
+            print(f"  Tokens:   {_trace_get(e, 'tokens') or 'N/A'}")
+            print(f"  Input:    {str(e.get('input', ''))[:300]}")
+            print(f"  Output:   {str(e.get('output', ''))[:300]}")
         else:
             print(f"Step {args.step} not found (total: {len(trace)})")
         return 0
@@ -526,20 +537,21 @@ def cmd_trace(args) -> int:
         cumulative = 0
         print("Time ──────────────────────────────────────────→")
         for e in trace:
-            ms = e["duration_ms"]
+            ms = _trace_get(e, "duration_ms")
             bar = "█" * max(1, int(ms / 100))
-            print(f"  {cumulative/1000:6.1f}s ├{bar} {e['term']} ({ms:.0f}ms)")
+            print(f"  {cumulative/1000:6.1f}s ├{bar} {_trace_get(e, 'term')} ({ms:.0f}ms)")
             cumulative += ms
         print(f"  {cumulative/1000:6.1f}s ┤ END")
         return 0
 
     # 默认:打印全部
     for e in trace:
-        inp = str(e["input"])[:60]
-        out = str(e["output"])[:60]
-        print(f"  β[{e['step']}] {e['term']} ({e['duration_ms']:.0f}ms): {inp} → {out}")
+        inp = str(e.get("input", ""))[:60]
+        out = str(e.get("output", ""))[:60]
+        ms = _trace_get(e, "duration_ms")
+        print(f"  β[{e.get('step', '?')}] {_trace_get(e, 'term')} ({ms:.0f}ms): {inp} → {out}")
 
-    total_ms = sum(e["duration_ms"] for e in trace)
+    total_ms = sum(_trace_get(e, "duration_ms") for e in trace)
     print(f"\nTotal: {len(trace)} β-reductions, {total_ms/1000:.1f}s")
     return 0