add uv install guide & pyproject.toml; unify error prefix to !!!Error:

This commit is contained in:
Liang Jiaqing
2026-04-24 10:00:15 +08:00
parent f334994ab6
commit 12c2fe1f79
3 changed files with 75 additions and 7 deletions

View File

@@ -89,6 +89,20 @@ cp mykey_template.py mykey.py
python launch.pyw python launch.pyw
``` ```
#### Method 2: uv (for experienced Python users)
If you prefer a modern Python workflow, GenericAgent also provides a minimal `pyproject.toml`:
```bash
git clone https://github.com/lsdefine/GenericAgent.git
cd GenericAgent
uv pip install -e ".[ui]" # Core + GUI dependencies
cp mykey_template.py mykey.py
python launch.pyw
```
> GenericAgent is meant to grow its environment through the Agent itself, not by pre-installing every possible package.
Full guide: [GETTING_STARTED.md](GETTING_STARTED.md) Full guide: [GETTING_STARTED.md](GETTING_STARTED.md)
--- ---
@@ -293,6 +307,20 @@ cp mykey_template.py mykey.py
python launch.pyw python launch.pyw
``` ```
#### 方法二uv 快速安装(熟悉 Python 的用户)
如果你习惯现代 Python 工作流GenericAgent 也提供了一个最小化的 `pyproject.toml`
```bash
git clone https://github.com/lsdefine/GenericAgent.git
cd GenericAgent
uv pip install -e ".[ui]" # 核心 + GUI 依赖
cp mykey_template.py mykey.py
python launch.pyw
```
> GenericAgent 更推荐由 Agent 在使用中自举环境,而不是预先手动装完整依赖。
完整引导流程见 [GETTING_STARTED.md](GETTING_STARTED.md)。 完整引导流程见 [GETTING_STARTED.md](GETTING_STARTED.md)。
📖 新手使用指南(图文版):[飞书文档](https://my.feishu.cn/wiki/CGrDw0T76iNFuskmwxdcWrpinPb) 📖 新手使用指南(图文版):[飞书文档](https://my.feishu.cn/wiki/CGrDw0T76iNFuskmwxdcWrpinPb)

View File

@@ -148,7 +148,7 @@ def _parse_claude_sse(resp_lines):
elif evt_type == "error": elif evt_type == "error":
err = evt.get("error", {}) err = evt.get("error", {})
emsg = err.get("message", str(err)) if isinstance(err, dict) else str(err) emsg = err.get("message", str(err)) if isinstance(err, dict) else str(err)
warn = f"\n\n[SSE Error: {emsg}]"; break warn = f"\n\n!!!Error: SSE {emsg}"; break
if not warn: if not warn:
if not got_message_stop and not stop_reason: warn = "\n\n[!!! 流异常中断,未收到完整响应 !!!]" if not got_message_stop and not stop_reason: warn = "\n\n[!!! 流异常中断,未收到完整响应 !!!]"
elif stop_reason == "max_tokens": warn = "\n\n[!!! Response truncated: max_tokens !!!]" elif stop_reason == "max_tokens": warn = "\n\n[!!! Response truncated: max_tokens !!!]"
@@ -211,7 +211,7 @@ def _parse_openai_sse(resp_lines, api_mode="chat_completions"):
elif etype == "error": elif etype == "error":
err = evt.get("error", {}) err = evt.get("error", {})
emsg = err.get("message", str(err)) if isinstance(err, dict) else str(err) emsg = err.get("message", str(err)) if isinstance(err, dict) else str(err)
if emsg: content_text += f"Error: {emsg}"; yield f"Error: {emsg}" if emsg: content_text += f"!!!Error: {emsg}"; yield f"!!!Error: {emsg}"
break break
elif etype == "response.completed": elif etype == "response.completed":
usage = evt.get("response", {}).get("usage", {}) usage = evt.get("response", {}).get("usage", {})
@@ -522,7 +522,7 @@ class BaseSession:
if block.get('type', '') == 'tool_use': if block.get('type', '') == 'tool_use':
tu = {'name': block.get('name', ''), 'arguments': block.get('input', {})} tu = {'name': block.get('name', ''), 'arguments': block.get('input', {})}
yield f'<tool_use>{json.dumps(tu, ensure_ascii=False)}</tool_use>' yield f'<tool_use>{json.dumps(tu, ensure_ascii=False)}</tool_use>'
if not content.startswith("Error:"): self.history.append({"role": "assistant", "content": [{"type": "text", "text": content}]}) if not content.startswith("!!!Error:"): self.history.append({"role": "assistant", "content": [{"type": "text", "text": content}]})
return _ask_gen() if stream else ''.join(list(_ask_gen())) return _ask_gen() if stream else ''.join(list(_ask_gen()))
class ClaudeSession(BaseSession): class ClaudeSession(BaseSession):
@@ -537,7 +537,7 @@ class ClaudeSession(BaseSession):
if r.status_code != 200: raise Exception(f"HTTP {r.status_code} {r.content.decode('utf-8', errors='replace')[:500]}") if r.status_code != 200: raise Exception(f"HTTP {r.status_code} {r.content.decode('utf-8', errors='replace')[:500]}")
return (yield from _parse_claude_sse(r.iter_lines())) or [] return (yield from _parse_claude_sse(r.iter_lines())) or []
except Exception as e: except Exception as e:
yield (err := f"Error: {e}") yield (err := f"!!!Error: {e}")
return [{"type": "text", "text": err}] return [{"type": "text", "text": err}]
def make_messages(self, raw_list): def make_messages(self, raw_list):
msgs = [{"role": m['role'], "content": list(m['content'])} for m in raw_list] msgs = [{"role": m['role'], "content": list(m['content'])} for m in raw_list]
@@ -621,7 +621,7 @@ class NativeClaudeSession(BaseSession):
elif b.get("type") == "thinking": yield "" elif b.get("type") == "thinking": yield ""
return content_blocks return content_blocks
except Exception as e: except Exception as e:
yield (err := f"Error: {e}") yield (err := f"!!!Error: {e}")
return [{"type": "text", "text": err}] return [{"type": "text", "text": err}]
def ask(self, msg): def ask(self, msg):
@@ -635,7 +635,7 @@ class NativeClaudeSession(BaseSession):
try: try:
while True: yield next(gen) while True: yield next(gen)
except StopIteration as e: content_blocks = e.value or [] except StopIteration as e: content_blocks = e.value or []
if content_blocks and not (len(content_blocks) == 1 and content_blocks[0].get("text", "").startswith("Error:")): if content_blocks and not (len(content_blocks) == 1 and content_blocks[0].get("text", "").startswith("!!!Error:")):
self.history.append({"role": "assistant", "content": content_blocks}) self.history.append({"role": "assistant", "content": content_blocks})
text_parts = [b["text"] for b in content_blocks if b.get("type") == "text"] text_parts = [b["text"] for b in content_blocks if b.get("type") == "text"]
content = "\n".join(text_parts).strip() content = "\n".join(text_parts).strip()
@@ -891,7 +891,7 @@ class MixinSession:
return self._cur_idx return self._cur_idx
def _raw_ask(self, *args, **kwargs): def _raw_ask(self, *args, **kwargs):
base, n = self._pick(), len(self._sessions) base, n = self._pick(), len(self._sessions)
test_error = lambda x: isinstance(x, str) and (x.startswith('Error:') or x.startswith('[Error:')) test_error = lambda x: isinstance(x, str) and x.lstrip().startswith(('!!!Error:', '[Error:'))
for attempt in range(self._retries + 1): for attempt in range(self._retries + 1):
idx = (base + attempt) % n idx = (base + attempt) % n
gen = self._orig_raw_asks[idx](*args, **kwargs) gen = self._orig_raw_asks[idx](*args, **kwargs)

40
pyproject.toml Normal file
View File

@@ -0,0 +1,40 @@
# AI install hint: choose deps by OS/env + needed ui/bot. do NOT install all.
# minimal core first; missing packages can be installed on demand.
[project]
name = "genericagent"
version = "0.1.0"
description = "Minimalist self-evolving autonomous agent framework"
readme = "README.md"
requires-python = ">=3.10,<3.14"
license = { text = "MIT" }
dependencies = [
"requests>=2.28",
"beautifulsoup4>=4.12",
"bottle>=0.12",
"simple-websocket-server>=0.4",
]
[project.optional-dependencies]
# select extras, not all. match current task/env.
# examples: .[ui] for GUI, bot deps only if bot needed.
ui = [
"streamlit>=1.28",
"pywebview>=4.0",
]
all-frontends = [
"python-telegram-bot>=20.0",
"qq-botpy>=1.0",
"pycryptodome>=3.19",
"qrcode>=7.4",
"lark-oapi>=1.0",
"wecom-aibot-sdk>=1.0",
"dingtalk-stream>=1.0",
]
[build-system]
requires = ["setuptools>=68.0"]
build-backend = "setuptools.build_meta"
[tool.setuptools]
py-modules = []