返回提交历史
Modified
g4f-go/fetch-python.sh
+38
-0
Modified
g4f-go/process.go
+15
-0
Modified
g4f-go/runtime.go
+7
-5
Modified
g4f/mcp/pa_provider.py
+35
-4
Modified
g4f/tools/optimize_request.py
+64
-0
XFEstudio/gpt4free
Lazy load pa providers
8b4edf83
代码差异
5 个文件
+159
-9
@@ -24,6 +24,14 @@ PBS_BASE="https://github.com/astral-sh/python-build-standalone/releases/download
24
24
PYORG_BASE="https://www.python.org/ftp/python/${PYVER}"
25
25
G4F_SRC="${G4F_SRC:-$HERE/..}" # gpt4free repository root
26
26
G4F_VERSION="${G4F_VERSION:-$(cd "$G4F_SRC" && python3 -c 'import sys;sys.path.insert(0,"g4f");from version import __version__;print(__version__)' 2>/dev/null || echo 0.4.x)}"
27
28
# Minimum versions for deps that have ancient pure-python releases on PyPI.
29
# `pip download --platform <x>` considers py3-none-any wheels valid for any
30
# target, so an old/misbehaving resolver can pick e.g. aiohttp 0.13.1 (2015,
31
# predates async/await) which crashes the embedded CPython 3.14. Pinning
32
# floors here keeps the offline wheel set sane.
33
WHEEL_FLOORS="aiohttp>=3.8"
34
27
35
WORK="$(mktemp -d)"
28
36
trap 'rm -rf "$WORK"' EXIT
29
37
@@ -99,12 +107,17 @@ do_platform() {
99
107
esac
100
108
mkdir -p "$dir/wheels"
101
109
if [ -n "$tag" ]; then
110
# Windows pip can't read process-substitution FDs from its subprocess, so
111
# write the version floors to a real file once per platform.
112
local floors="$dir/wheels.floors"
113
printf '%s\n' "$WHEEL_FLOORS" > "$floors"
102
114
# Full dependency resolution (no --no-deps) so the runtime's offline
103
115
# `pip install g4f` finds every transitive wheel it needs.
104
116
# brotli is optional in g4f and has no cp314 wheel for win_arm64;
105
117
# fall back to fetching everything else if a single dep is unavailable.
106
118
if ! python3 -m pip download \
107
119
-r "$G4F_SRC/requirements-min.txt" \
120
--constraint "$floors" \
108
121
--only-binary=:all: \
109
122
--python-version "$PYVER" --implementation cp --abi "cp$(echo "$PYVER" | tr -d '.')" \
110
123
--platform "$tag" \
@@ -113,6 +126,7 @@ do_platform() {
113
126
grep -v '^brotli$' "$G4F_SRC/requirements-min.txt" > "$dir/req-nobrotli.txt"
114
127
python3 -m pip download \
115
128
-r "$dir/req-nobrotli.txt" \
129
--constraint "$floors" \
116
130
--only-binary=:all: \
117
131
--python-version "$PYVER" --implementation cp --abi "cp$(echo "$PYVER" | tr -d '.')" \
118
132
--platform "$tag" \
@@ -120,6 +134,30 @@ do_platform() {
120
134
echo " WARNING: wheel download for $name failed; runtime will need network on first run" >&2
121
135
}
122
136
fi
137
# Belt-and-braces: drop any wheel whose Requires-Python metadata excludes
138
# our interpreter. The --constraint above prevents this at resolve time;
139
# this catches stale wheels (e.g. copied in from wheels-cache) and covers
140
# ancient pure-python releases like aiohttp 0.13.1.
141
python3 - "$dir/wheels" "$PYVER" <<'PY' || true
142
import glob, os, sys, zipfile
143
from packaging.specifiers import SpecifierSet
144
from packaging.version import Version
145
want_v = Version(sys.argv[2])
146
for whl in glob.glob(os.path.join(sys.argv[1], "*.whl")):
147
try:
148
with zipfile.ZipFile(whl) as z:
149
meta = next((n for n in z.namelist() if n.endswith(".dist-info/METADATA")), None)
150
if not meta:
151
continue
152
txt = z.read(meta).decode("utf-8", "replace")
153
rp = next((l.split(":", 1)[1].strip() for l in txt.splitlines()
154
if l.lower().startswith("requires-python:")), None)
155
if rp and not SpecifierSet(rp).contains(want_v):
156
print(f" removing {os.path.basename(whl)} (Requires-Python {rp} excludes {sys.argv[2]})")
157
os.remove(whl)
158
except Exception as e:
159
print(f" skip check {os.path.basename(whl)}: {e}")
160
PY
123
161
fi
124
162
125
163
# 3) Merge g4f package + wheels so the interpreter is self-contained.
@@ -123,10 +123,25 @@ func extractZip(r io.ReaderAt, size int64, dest string) error {
123
123
}
124
124
125
125
// pythonExecutable returns the launcher (unix) or python.exe (windows) path.
126
//
127
// The embedded archive is laid out as <name>/python-home/<exe>, so after
128
// extraction the interpreter lives at binDir/python-home/python.exe on
129
// windows and binDir/python-home/bin/python on unix. We prefer that location
130
// and fall back to the legacy binDir/python(.exe) layout produced by older
131
// archives, mirroring the shell launcher's logic.
126
132
func pythonExecutable(binDir string) string {
133
home := filepath.Join(binDir, "python-home")
127
134
if runtime.GOOS == "windows" {
135
exe := filepath.Join(home, "python.exe")
136
if fi, err := os.Stat(exe); err == nil && !fi.IsDir() {
137
return exe
138
}
128
139
return filepath.Join(binDir, "python.exe")
129
140
}
141
exe := filepath.Join(home, "bin", "python")
142
if fi, err := os.Stat(exe); err == nil && !fi.IsDir() {
143
return exe
144
}
130
145
return filepath.Join(binDir, "python")
131
146
}
132
147
@@ -180,13 +180,15 @@ func installG4F(binDir, exe string, start time.Time) error {
180
180
// pipEnv restricts pip to the embedded runtime so the first run never touches
181
181
// the network. A future `g4f-go install g4f --upgrade` can relax this.
182
182
func pipEnv(binDir string) []string {
183
p := pythonExecutable(binDir)
184
lib := filepath.Join(binDir, "Lib", "site-packages")
185
if strings.Contains(p, "bin/python") {
186
lib = filepath.Join(binDir, "lib", "python3.14", "site-packages")
183
home := pythonHome(binDir)
184
// pbs installs are a full layout: lib/pythonX.Y/site-packages (unix) or
185
// Lib/site-packages (windows) inside the interpreter home.
186
lib := filepath.Join(home, "Lib", "site-packages")
187
if runtime.GOOS != "windows" {
188
lib = filepath.Join(home, "lib", "python3.14", "site-packages")
187
189
}
188
190
return []string{
189
"PYTHONHOME=" + pythonHome(binDir),
191
"PYTHONHOME=" + home,
190
192
"PYTHONNOUSERSITE=1",
191
193
"PYTHONDONTWRITEBYTECODE=1",
192
194
"PYTHONUTF8=1",
@@ -67,7 +67,7 @@ import traceback
67
67
import types
68
68
import builtins as _builtins
69
69
from pathlib import Path
70
from typing import Any, Dict, FrozenSet, List, Optional, Type
70
from typing import Any, Dict, FrozenSet, List, Optional, Tuple, Type
71
71
from .. import debug
72
72
73
73
# ---------------------------------------------------------------------------
@@ -748,7 +748,7 @@ def load_pa_provider(file_path: "str | Path") -> Optional[Type]:
748
748
raise ValueError(f"File must have .pa.py extension: {file_path}")
749
749
750
750
code = file_path.read_text(encoding="utf-8")
751
result = execute_safe_code(code, file_path=file_path)
751
result = execute_safe_code(code, file_path=file_path, timeout=0.1, max_depth=100)
752
752
753
753
if not result.success:
754
754
raise RuntimeError(
@@ -770,7 +770,7 @@ def load_pa_provider(file_path: "str | Path") -> Optional[Type]:
770
770
return None
771
771
772
772
773
def list_pa_providers(directory: "Optional[str | Path]" = None) -> List[Path]:
773
def list_pa_providers(directory: "Optional[str | Path]" = None) -> Tuple[Path, List[Path]]:
774
774
"""Return all ``.pa.py`` files found (recursively) in *directory*.
775
775
776
776
Args:
@@ -830,10 +830,39 @@ class PaProviderRegistry:
830
830
if _time_module.monotonic() - self._loaded_at >= self.TTL:
831
831
self.refresh()
832
832
833
def _ensure_index(self) -> None:
834
if not self._entries:
835
self.index()
836
833
837
# ------------------------------------------------------------------
834
838
# Public API
835
839
# ------------------------------------------------------------------
836
840
841
def index(self) -> None:
842
"""Re-scan the workspace and reload all ``.pa.py`` providers."""
843
entries: List[tuple] = []
844
directory, pa_paths = list_pa_providers()
845
for pa_path in pa_paths:
846
try:
847
relative_path = pa_path.relative_to(directory).as_posix()
848
provider_id = self._make_id(pa_path)
849
entries.append(
850
(
851
provider_id,
852
None,
853
None,
854
True,
855
None,
856
None,
857
relative_path,
858
)
859
)
860
except Exception as e:
861
debug.error(f"Failed to load PA provider from {pa_path}:", e)
862
pass
863
self._entries = entries
864
865
837
866
def refresh(self) -> None:
838
867
"""Re-scan the workspace and reload all ``.pa.py`` providers."""
839
868
entries: List[tuple] = []
@@ -893,9 +922,11 @@ class PaProviderRegistry:
893
922
894
923
def get_provider_class(self, provider_id: str) -> Optional[Type]:
895
924
"""Return the provider class for *provider_id*, or ``None``."""
896
self._ensure_fresh()
925
self._ensure_index()
897
926
for e in self._entries:
898
927
if e[0] == provider_id:
928
if e[5] is None:
929
return load_pa_provider(get_workspace_dir() / e[6])
899
930
return e[5]
900
931
return None
901
932
@@ -111,6 +111,70 @@ REPLACE_VSC = (
111
111
"- **Skills:** Use `read_file` to load detailed skill instructions when a "
112
112
"task matches a skill's domain (e.g., use `project-setup-info-local` for "
113
113
"scaffolding new projects from scratch, not for adding individual files)."
114
"""
115
# ADHD Mode: Escaping the Textbook Answer
116
117
The first three answers to any problem are obvious, safe, and forgettable. The interesting answers live past number three, in the "awkward middle." This skill forces the model there using parallel divergent ideation.
118
119
## Pre-flight Gate (Cost Check)
120
This process costs ~10 parallel Agent calls (5-10x normal). Run this gate first.
121
1. **Explicit Invocation:** If invoked via `/adhd` or explicit user request, **skip to Phase 1**.
122
2. **Self-Judge:** Abort and answer directly unless ALL three are true:
123
- **Open-ended:** Multiple viable answers exist (no canonical answer).
124
- **High-stakes:** Architecture, public APIs, fuzzy bugs (not a 11pm side-project).
125
- **Open phrasing:** User didn't use words like "quick," "standard," "just," or "textbook."
126
*(If aborted, append: "Run `/adhd <your problem>` for wider exploration.")*
127
128
## The Loop
129
Strict separation is required; the critic must not strangle the generator.
130
131
### Phase 1 — Diverge (No Critic)
132
1. Pick 5 cognitive frames from the table below. Bias toward `code`/`design` for technical problems; always include 1 `wild`.
133
2. Spawn 5 **parallel, isolated** Agent/Task calls (one per frame). *Critical invariant: Do NOT serialize or share context between branches.*
134
3. **Agent Instruction:**
135
> DIVERGENT MODE: Generate 6 short, distinct ideas under this frame. The first 3 obvious answers are banned. No evaluation, ranking, or hedging. Push into the awkward middle. Output JSON array only: `[{"text": "...", "rationale": "..."}]`
136
137
### Phase 2 — Focus (Critic On)
138
1. **Score:** Rate ideas 0-10 on Novelty (0.35 weight), Viability (0.40), Fit (0.25). Flag traps (hidden costs, false economies) with a one-line reason.
139
2. **Cluster:** Group into 3-6 clusters by underlying angle (e.g., "cache-shaped plays").
140
3. **Deepen Top 3:** Spawn 1 Agent call per top idea (excluding traps). **Agent Instruction:**
141
> FOCUS MODE: Sketch how this works (4-8 sentences). Name the load-bearing risk. Name the first concrete coding step. Generate 3-5 child ideas (variations/unlocks). Output JSON only.
142
143
## Cognitive Frames (Pick 5 per run)
144
| Frame | Vantage Prompt | Tags |
145
|---|---|---|
146
| **Hardware engineer** | Think in latency, memory layout, physical constraints. What does bus topology/cache/timing tell you? | code, wild |
147
| **Regulator** | Audit for compliance/failure modes. What must be provable, traceable, or refusable here? | design, general |
148
| **10-year-old** | Naive but unencumbered. Ignore convention. | general, wild |
149
| **Hostile competitor** | Exploit, fail, or sabotage the obvious solution. Then invert into ideas. | code, design |
150
| **Biology** | Transplant a mechanism from biology (immune systems, cell signaling). Force-fit it. | code, wild |
151
| **Logistics** | Steal from logistics: queues, batching, JIT, hub-and-spoke. Apply literally. | code, design |
152
| **Game design** | What are the loops, rewards, friction, speedrun tricks? Treat the user as a player. | design, general |
153
| **Markets** | Treat as a market. Buyers, sellers, auction, clearing house—what do they look like here? | design, wild |
154
| **Inversion** | Brainstorm how to guarantee NOT X. Then negate each answer back. | code, design, general |
155
| **Extreme: $0, 1hr** | Crudest version that still does the load-bearing thing? | code, general |
156
| **Extreme: ∞ budget, 10yrs** | Maximalist version? | design, wild |
157
| **Remove assumption** | Name the thing everyone treats as fixed. Imagine it is gone. What is possible? | code, design, wild |
158
| **Speedrunner** | Find glitches, skips, out-of-bounds tricks. What is the abusive-but-legal path? | code, wild |
159
| **Ant colony** | No central planner. Many dumb agents, local rules. How does this solve itself emergently? | code, wild |
160
| **3am on-call** | You are woken at 3am when this breaks. What design prevents the page? | code, design |
161
162
## Output Shape
163
1. **Brief:** 1-2 lines confirming the problem/reframe.
164
2. **Wide Set:** Full pool grouped by cluster (labeled by angle). Short phrases with score chips `[N7 V8 F9]`.
165
3. **Converge:** 2-4 idea shortlist with rationale. Mark non-obvious viable picks with ★. List traps separately with 1-line reasons.
166
4. **Focus:** The 3 deepened branches (sketch, risk, first step, child ideas).
167
5. **Provocation:** 1 wildcard question opening a new direction.
168
169
## Anti-patterns & Calibration
170
- **No fake divergence:** 10 variations of one assumption isn't breadth; it's decoration.
171
- **No weird-for-weird's-sake:** Always converge with a real opinion. "You decide" is a cop-out.
172
- **Strict Isolation:** Simulating parallel branches in one context just creates a wider single thought. Use separate Agent contexts.
173
- **Structure over prose:** Cluster, label, and score. Walls of text are useless.
174
- **Calibrate:** Scale ideas to stakes (3x4 for quick tasks, 5x8 for strategy). Stop diverging when candidates repeat shapes.
175
176
*(Note: Use companion CLI `npm install -g adhd-agent` for batch/outside-Claude runs).*
177
"""
114
178
)
115
179
116
180