Skip to content

Commit 87d9b83

Browse files
Merge pull request #100 from multimindlab/feature-fix-API
Fix existing features and improve production readiness
2 parents 6461ec1 + cd046c4 commit 87d9b83

280 files changed

Lines changed: 5784 additions & 1369138 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -215,11 +215,12 @@ jobs:
215215
# Full suite with everything installed, including the framework-interop
216216
# extras (langchain-core, llama-index-core) and the MCP SDK (already part
217217
# of `all`). This is the only job where tests that `importorskip` those
218-
# packages actually execute instead of skipping. Acts as the historical
219-
# "must reach 95% pass rate" gate.
218+
# packages actually execute instead of skipping. Enforces two gates: a
219+
# 95% pass-rate threshold (see the Python step below) and a 20% line
220+
# coverage floor (--cov-fail-under=20) — NOT a 95% coverage gate.
220221
# ---------------------------------------------------------------------------
221222
test-full:
222-
name: test-full (coverage + 95% gate)
223+
name: test-full (95% pass-rate gate, 20% coverage floor)
223224
runs-on: ubuntu-latest
224225
needs: [test-core]
225226
steps:
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
name: Publish (develop)
2+
3+
# Auto-publish to PyPI when a PR merges into `develop` with a version bump.
4+
#
5+
# PyPI refuses to re-upload an existing version, so the pipeline publishes
6+
# only when `multimind.__version__` differs from the latest release on PyPI.
7+
# Merges that don't bump the version (docs, CI, refactors) skip publishing
8+
# cleanly. The PyPI project description is rebuilt from README.md on every
9+
# publish (pyproject `readme = "README.md"`), so the PyPI page text always
10+
# matches the merged README.
11+
#
12+
# Requires a one-time trusted-publisher registration on pypi.org for THIS
13+
# workflow file (owner: multimindlab, repo: multimind-sdk,
14+
# workflow: publish-develop.yml, environment: pypi) — trusted publishing is
15+
# registered per workflow filename, so the existing release.yml registration
16+
# does not cover this file.
17+
#
18+
# The manual flow in release.yml (publish on a GitHub release) still works
19+
# unchanged; this workflow tags the release itself, and tags/releases created
20+
# with GITHUB_TOKEN do not re-trigger release.yml, so a double publish is
21+
# impossible.
22+
23+
on:
24+
push:
25+
branches:
26+
- develop
27+
workflow_dispatch:
28+
29+
concurrency:
30+
group: publish-develop
31+
cancel-in-progress: false
32+
33+
env:
34+
PIP_DISABLE_PIP_VERSION_CHECK: "1"
35+
36+
jobs:
37+
# ---------------------------------------------------------------------------
38+
# Publish only when the version in the repo is new to PyPI.
39+
# ---------------------------------------------------------------------------
40+
check-version:
41+
name: Check for version bump
42+
runs-on: ubuntu-latest
43+
outputs:
44+
should_publish: ${{ steps.check.outputs.should_publish }}
45+
version: ${{ steps.check.outputs.version }}
46+
steps:
47+
- uses: actions/checkout@v4
48+
- uses: actions/setup-python@v5
49+
with:
50+
python-version: "3.11"
51+
- name: Compare repo version against PyPI
52+
id: check
53+
run: |
54+
python - <<'EOF' >> "$GITHUB_OUTPUT"
55+
import json, re, urllib.request
56+
57+
src = open("multimind/__init__.py", encoding="utf-8").read()
58+
local = re.search(r'^__version__\s*=\s*["\']([^"\']+)["\']', src, re.M).group(1)
59+
60+
with urllib.request.urlopen(
61+
"https://pypi.org/pypi/multimind-sdk/json", timeout=30
62+
) as resp:
63+
data = json.load(resp)
64+
published = set(data.get("releases", {}))
65+
66+
should = local not in published
67+
print(f"version={local}")
68+
print(f"should_publish={str(should).lower()}")
69+
EOF
70+
- name: Report
71+
run: |
72+
if [ "${{ steps.check.outputs.should_publish }}" = "true" ]; then
73+
echo "Version ${{ steps.check.outputs.version }} is new — will publish."
74+
else
75+
echo "Version ${{ steps.check.outputs.version }} already on PyPI — skipping publish."
76+
fi
77+
78+
# ---------------------------------------------------------------------------
79+
# Build sdist + wheel, validate metadata with twine. Mirrors release.yml.
80+
# ---------------------------------------------------------------------------
81+
build:
82+
name: Build distribution
83+
runs-on: ubuntu-latest
84+
needs: [check-version]
85+
if: needs.check-version.outputs.should_publish == 'true'
86+
steps:
87+
- uses: actions/checkout@v4
88+
- uses: actions/setup-python@v5
89+
with:
90+
python-version: "3.11"
91+
cache: pip
92+
- name: Install build tools
93+
run: |
94+
python -m pip install --upgrade pip
95+
pip install build twine
96+
- name: Build sdist + wheel
97+
run: python -m build
98+
- name: Validate distribution metadata (twine check)
99+
run: twine check dist/*
100+
- name: Upload distribution artifacts
101+
uses: actions/upload-artifact@v4
102+
with:
103+
name: dist
104+
path: dist/
105+
if-no-files-found: error
106+
107+
# ---------------------------------------------------------------------------
108+
# Install the built wheel in a clean environment and smoke-test it —
109+
# catches packaging mistakes an editable install never would.
110+
# ---------------------------------------------------------------------------
111+
test-wheel:
112+
name: Test built wheel
113+
runs-on: ubuntu-latest
114+
needs: [build]
115+
steps:
116+
- uses: actions/checkout@v4
117+
- uses: actions/setup-python@v5
118+
with:
119+
python-version: "3.11"
120+
cache: pip
121+
- name: Download distribution artifacts
122+
uses: actions/download-artifact@v4
123+
with:
124+
name: dist
125+
path: dist/
126+
- name: Install the built wheel (+ test runner only, no editable install)
127+
run: |
128+
python -m pip install --upgrade pip
129+
python -m pip install dist/*.whl
130+
python -m pip install "pytest>=9.0.3" "pytest-asyncio>=0.21.0"
131+
- name: Import smoke test
132+
run: python -c "import multimind; print('multimind', multimind.__version__)"
133+
- name: Run smoke test subset against the installed wheel
134+
run: |
135+
pytest tests/test_import.py tests/test_basic.py -v --tb=short \
136+
-m "not integration and not slow and not requires_api_key"
137+
138+
# ---------------------------------------------------------------------------
139+
# Publish to PyPI via OIDC trusted publishing, then tag the commit and
140+
# create a GitHub release so the published version is traceable in git.
141+
# ---------------------------------------------------------------------------
142+
publish:
143+
name: Publish to PyPI
144+
runs-on: ubuntu-latest
145+
needs: [check-version, test-wheel]
146+
environment:
147+
name: pypi
148+
url: https://pypi.org/project/multimind-sdk/
149+
permissions:
150+
id-token: write # OIDC trusted publishing — no API token needed
151+
contents: write # push the version tag + create the GitHub release
152+
steps:
153+
- uses: actions/checkout@v4
154+
- name: Download distribution artifacts
155+
uses: actions/download-artifact@v4
156+
with:
157+
name: dist
158+
path: dist/
159+
- name: Publish to PyPI
160+
uses: pypa/gh-action-pypi-publish@release/v1
161+
with:
162+
packages-dir: dist/
163+
- name: Tag and create GitHub release
164+
env:
165+
GH_TOKEN: ${{ github.token }}
166+
VERSION: ${{ needs.check-version.outputs.version }}
167+
run: |
168+
git tag "v${VERSION}" "${GITHUB_SHA}"
169+
git push origin "v${VERSION}"
170+
gh release create "v${VERSION}" dist/* \
171+
--title "v${VERSION}" \
172+
--generate-notes \
173+
--target "${GITHUB_SHA}"

.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,3 +115,10 @@ multimind-docs/node_modules/
115115
multimind-docs/.next/
116116
multimind-docs/out/
117117
multimind-docs/build/
118+
119+
# Runtime/generated artifacts — not source, shouldn't be tracked
120+
models/
121+
guardrails.json
122+
123+
# Private frontend workspace — never push to this repo
124+
multimind-frontend/

examples/advanced/roadmap_feature_examples.py

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,29 @@
11
import torch
2-
from multimind.client.model_client import (
3-
LSTMModelClient, DynamicMoEModelClient, MultiModalClient, ImageModelClient, AudioModelClient, VideoModelClient, CodeModelClient
4-
)
5-
from multimind.llm.non_transformer_llm import MambaLLM
6-
from multimind.agents.react_toolchain import ReasoningChain, ReasoningStep
2+
73
from multimind.agents.agent_registry import AgentRegistry
84
from multimind.agents.prompt_correction import PromptCorrectionLayer
5+
from multimind.agents.react_toolchain import ReasoningChain, ReasoningStep
96
from multimind.client.federated_router import FederatedRouter
7+
from multimind.client.model_client import (
8+
AudioModelClient,
9+
CodeModelClient,
10+
DynamicMoEModelClient,
11+
ImageModelClient,
12+
LSTMModelClient,
13+
MultiModalClient,
14+
VideoModelClient,
15+
)
1016
from multimind.fine_tuning.rag_fine_tuner import RAGFineTuner
17+
from multimind.llm.non_transformer_llm import MambaLLM
18+
1119

1220
# --- DynamicMoEModelClient Example ---
1321
class DummyClient:
1422
def __init__(self, name): self.name = name
1523
def generate(self, prompt, **kwargs): return f"[{self.name} output for: {prompt}]"
1624
def router_fn(prompt, metrics):
17-
if len(prompt) > 10: return "slow"
25+
if len(prompt) > 10:
26+
return "slow"
1827
return "fast"
1928
moe_client = DynamicMoEModelClient({"fast": DummyClient("fast"), "slow": DummyClient("slow")}, router_fn)
2029
print("DynamicMoEModelClient (short):", moe_client.generate("hi"))
@@ -84,7 +93,9 @@ def adapter_updater(adapter_key, new_path): print(f"Adapter {adapter_key} update
8493

8594
# --- RAGFineTuner Example ---
8695
def dummy_rag_pipeline(query): return {"context": f"[Context for: {query}]", "answer": f"[Answer for: {query}]"}
87-
def dummy_fine_tune(train_data, **kwargs): print(f"Fine-tuning on {len(train_data)} examples."); return "fine-tuned-model"
96+
def dummy_fine_tune(train_data, **kwargs):
97+
print(f"Fine-tuning on {len(train_data)} examples.")
98+
return "fine-tuned-model"
8899
rag_ft = RAGFineTuner(dummy_rag_pipeline, dummy_fine_tune)
89100
queries = ["What is the capital of France?", "Who wrote Hamlet?"]
90-
print("RAGFineTuner result:", rag_ft.auto_ft_from_rag(queries, n_per_query=2))
101+
print("RAGFineTuner result:", rag_ft.auto_ft_from_rag(queries, n_per_query=2))

examples/agents/agent_registry_example.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,4 @@ def agent_b(query, state=None):
1313
registry.set_fallback("a", "b")
1414
print(registry.run_agent("a", "hello"))
1515
print(registry.run_agent("a", "fail this"))
16-
print("State after session:", registry.get_state(None))
16+
print("State after session:", registry.get_state(None))

examples/agents/react_toolchain_example.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Example usage for demonstration purposes only
22
from multimind.agents.react_toolchain import ReasoningChain, ReasoningStep
33

4+
45
def retrieve(query, context=None):
56
return f"[Retrieved context for: {query}]"
67
def generate(prompt, context=None):
@@ -19,4 +20,4 @@ def print_hook(step, inp, out):
1920
print(f"Step: {step.name}, Input: {inp}, Output: {out}")
2021
chain.add_hook(print_hook)
2122
result = chain.run("What is 2+2?")
22-
print("Final result:", result)
23+
print("Final result:", result)

0 commit comments

Comments
 (0)