diff --git a/README.md b/README.md index d9e5149..4fa8ef5 100644 --- a/README.md +++ b/README.md @@ -89,6 +89,20 @@ cp mykey_template.py mykey.py 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) --- @@ -293,6 +307,20 @@ cp mykey_template.py mykey.py 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)。 📖 新手使用指南(图文版):[飞书文档](https://my.feishu.cn/wiki/CGrDw0T76iNFuskmwxdcWrpinPb) diff --git a/llmcore.py b/llmcore.py index f290884..2a84677 100644 --- a/llmcore.py +++ b/llmcore.py @@ -148,7 +148,7 @@ def _parse_claude_sse(resp_lines): elif evt_type == "error": err = evt.get("error", {}) 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 got_message_stop and not stop_reason: warn = "\n\n[!!! 流异常中断,未收到完整响应 !!!]" 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": err = evt.get("error", {}) 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 elif etype == "response.completed": usage = evt.get("response", {}).get("usage", {}) @@ -522,7 +522,7 @@ class BaseSession: if block.get('type', '') == 'tool_use': tu = {'name': block.get('name', ''), 'arguments': block.get('input', {})} yield f'{json.dumps(tu, ensure_ascii=False)}' - 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())) 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]}") return (yield from _parse_claude_sse(r.iter_lines())) or [] except Exception as e: - yield (err := f"Error: {e}") + yield (err := f"!!!Error: {e}") return [{"type": "text", "text": err}] def make_messages(self, 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 "" return content_blocks except Exception as e: - yield (err := f"Error: {e}") + yield (err := f"!!!Error: {e}") return [{"type": "text", "text": err}] def ask(self, msg): @@ -635,7 +635,7 @@ class NativeClaudeSession(BaseSession): try: while True: yield next(gen) 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}) text_parts = [b["text"] for b in content_blocks if b.get("type") == "text"] content = "\n".join(text_parts).strip() @@ -891,7 +891,7 @@ class MixinSession: return self._cur_idx def _raw_ask(self, *args, **kwargs): 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): idx = (base + attempt) % n gen = self._orig_raw_asks[idx](*args, **kwargs) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..277f5db --- /dev/null +++ b/pyproject.toml @@ -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 = [] \ No newline at end of file