Release 0.13.3a2 - #69
Open
github-actions[bot] wants to merge 64 commits into
Open
Conversation
chore: Configure Renovate
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
* chore: migrate setup.py→pyproject.toml, consolidate CI workflows Replace setup.py with pyproject.toml (dynamic version from version.py). Add build_tests, license_tests, lint, pip_audit workflows using OpenVoiceOS/gh-automations@dev reusable workflows. Migrate publish_stable and release_workflow from TigreGotico/gh-automations@master to OpenVoiceOS/gh-automations@dev; add workflow_dispatch trigger and bot-safety guard. split from #25 * fix: define __version__ attr required by pyproject dynamic version * test: guard dynamic-version contract (version.py ↔ __version__ fields) Asserts VERSION_* integers are non-negative, __version__ is correctly derived from them, and the string matches PEP-440 format. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…t in chat.py (#40) * fix: declare uvicorn+ovos-workshop deps; restore py3.9 compat in chat.py Fixes #39 (partial — OPM lang-detector fallback fix in ovos-lang-detector-classics-plugin, see fix/issue-39-cld2-exception branch there) (a) Add uvicorn and ovos-workshop to [project].dependencies in pyproject.toml. Both are imported at runtime (__main__.py and ollama.py respectively) but were not declared, causing ImportError in clean environments. (b) Replace two multi-line f-string expressions in streaming_completion_response (chat.py lines ~285 and ~302) with intermediate dict variables. Multi-line dict literals inside f-string {} require Python 3.12 (PEP 701) but the package declares requires-python >= 3.9. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Delete tests/test_issue_39.py --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…aths (#29) * feat: vendor-prefixed OpenAI/Ollama routers + deprecated legacy paths * test: cover deprecated paths middleware and route registration (split/router-infra) - Verify Deprecation + Link headers on /v1 and /api legacy paths - Verify canonical paths receive no deprecation headers - Cover _build_successor_path mapping and passthrough - Cover FastAPI 422 responses for malformed/missing bodies - Smoke-test register_deprecated_routes mounts /v1/ legacy paths Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: normalize Google-style docstrings in chat and ollama routers Remove ``(Type)`` annotations from Args/Returns sections (redundant with function signatures). Condense multi-line boilerplate docstrings on lifespan managers and inner streaming generators to one-liners. No logic changes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* feat: A2A server endpoint (/a2a) with agent card + executor * test: full A2A server coverage — agent card, executor, cancel, SDK-absent path, invalid base_url Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: improve OVOSPersonaAgentExecutor.__init__ docstring Add summary sentence to the __init__ docstring; no logic changes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
) * feat(compat): Anthropic Claude-compatible endpoints (/anthropic/v1) * test: full Anthropic compat coverage — schema, multi-turn, system, streaming, errors Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test: real-SDK e2e against live server + enable CI test execution Add tests/e2e/test_e2e_anthropic.py driving the official anthropic SDK (Anthropic(base_url=...)) against a live uvicorn-served app, covering non-streaming, system prompt, multi-turn and streaming. Add anthropic to the dev extra and wire build_tests.yml to install it and run the suite. * test: use asyncio.run in A2A executor tests (py3.14 compatibility) asyncio.get_event_loop() raises on Python 3.14 (and whenever a prior test closes the main-thread loop); switch the A2A executor tests to asyncio.run so the suite is loop-state independent. --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* feat(compat): Google Gemini-compatible endpoints (/gemini/v1beta) * test: full Gemini compat coverage — schema, multi-turn, system-instruction, streaming, errors Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test: real-SDK e2e against live server + enable CI test execution Add tests/e2e/test_e2e_gemini.py driving the official google-genai SDK (genai.Client(http_options=HttpOptions(base_url=...))) against a live uvicorn-served app, covering non-streaming, system instruction, multi-turn and streaming. Add google-genai to the dev extra and wire build_tests.yml to install it and run the suite. * test: use asyncio.run in A2A executor tests (py3.14 compatibility) asyncio.get_event_loop() raises on Python 3.14 (and whenever a prior test closes the main-thread loop); switch the A2A executor tests to asyncio.run so the suite is loop-state independent. --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* feat(compat): AWS Bedrock-compatible endpoints (/bedrock/model) * test: full AWS Bedrock compat coverage — all model families, invoke, stream, converse, schema errors Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(compat): emit AWS event-stream framing for Bedrock streaming invoke-with-response-stream now encodes vnd.amazon.eventstream binary frames (prelude/headers/payload with CRC32 checksums) instead of SSE, so boto3's invoke_model_with_response_stream parses the stream natively. Add tests/e2e/test_e2e_bedrock.py driving the official boto3 bedrock-runtime client (invoke_model for Claude + Titan, converse, streaming). Add boto3 to the dev extra and wire build_tests.yml to run the suite. * test(compat): decode Bedrock stream as event-stream frames in unit tests Update the invoke-with-response-stream unit tests to decode the vnd.amazon.eventstream binary framing via botocore's EventStreamBuffer, matching the streaming format boto3 consumes. * test: use asyncio.run in A2A executor tests (py3.14 compatibility) asyncio.get_event_loop() raises on Python 3.14 (and whenever a prior test closes the main-thread loop); switch the A2A executor tests to asyncio.run so the suite is loop-state independent. --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* feat(compat): HuggingFace TGI-compatible endpoints (/tgi) * test: full TGI compat coverage — schema, generate, streaming, info/health, errors Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(compat): serve TGI generation at endpoint root for InferenceClient The maintained huggingface_hub InferenceClient posts to the bare endpoint URL and selects streaming via the body 'stream' flag; add a root dispatch route delegating to the shared non-streaming/streaming handlers so the official client works against the server unchanged. Add tests/e2e/test_e2e_tgi.py driving InferenceClient (non-stream, details, stream) plus /health, /info and the native /generate route. Add huggingface_hub to the dev extra and wire build_tests.yml to run the suite. * test: use asyncio.run in A2A executor tests (py3.14 compatibility) asyncio.get_event_loop() raises on Python 3.14 (and whenever a prior test closes the main-thread loop); switch the A2A executor tests to asyncio.run so the suite is loop-state independent. --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* docs: user-facing documentation for all API surfaces * docs: fix broken links in index.md; add docs column to API table Remove references to a2a.md and bedrock-models.md (not yet written). Add a Docs column to the API surfaces table pointing to anchors in api-compatibility.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
The /openai/v1 and /ollama/api surfaces shipped with only mocked unit tests. Add e2e tests driving the official openai and ollama clients against a live server (chat/completions/models and chat/generate/tags, streaming included). Fix an Ollama-compat bug the e2e surfaced: non-streaming /generate returned a chat 'message' object instead of the 'response' field the official client reads (the streaming path already used 'response'). Add openai+ollama to the dev extra and enable CI test execution.
The repo had two parallel trees: tests/ (run by CI's test_path) and a stale test/unittests/ that duplicated the compat tests already in tests/. Drop the duplicates and keep the two unique files — test_a2a.py becomes test_a2a_unit.py and test_compat_routers.py moves into tests/ (its /generate assertion updated to the 'response' field the endpoint now returns). One test directory remains.
* feat: OpenAI-compatible Files + Vector Stores API on the current app factory Re-architect the RAG subsystem onto dev's prefixed-router structure: - Mount files (/openai/v1/files) and vector_stores (/openai/v1/vector_stores) routers; the /embeddings path is already served by chat_router, so the duplicate embeddings router is not mounted (its get_text_embeddings helper still powers vector-store search via a dedicated embeddings plugin). - Initialise the SQLite schema from a single app-level lifespan (init_db); drop the per-router lifespans that eagerly loaded embedding plugins and crashed startup when those plugins were absent. - Add the SQLite/file deps (sqlalchemy, aiosqlite, python-dotenv, python-multipart) to the core requirements and a 'rag' extra for the chunker/reranker plugins. - Add tests/e2e/test_e2e_rag.py driving the official openai SDK against a live server for the files and vector-store lifecycle; enable CI test execution and use asyncio.run in the A2A tests for py3.14. Replaces the pre-refactor feat/rag history, which predated the vendor-prefixed routers, deprecated-route shims and A2A endpoint now on dev. * feat: unify embeddings behind one swappable backend across all routers The OpenAI /openai/v1/embeddings, Ollama /ollama/api/embed (batch) and legacy /ollama/api/embeddings (prompt) endpoints now delegate to a single get_embeddings_backend dependency — the embeddings analogue of the shared persona. Configure it via TEXT_EMBEDDINGS_PLUGIN / EMBEDDINGS_URL / EMBEDDINGS_MODEL to point at any embeddings service, with a persona-solver fallback when no plugin is available. Adds the /ollama/api/embed endpoint and a faithful legacy /ollama/api/embeddings shape so the official ollama client works, and base64 encoding so the official openai client works. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(rag): add real cross-plugin e2e test for GGUFEmbeddings + chromadb/qdrant RAG path - config.py: pass `model` into gguf embeddings config (defaults to all-MiniLM-L6-v2) so CI uses the 46 MB 384-dim model instead of labse - vector_stores.py: store chunk `content` in embedding metadata so search results can return it (was commented out, causing KeyError) - tests/e2e/test_e2e_rag_plugins.py: real no-mock RAG e2e parametrised over chromadb and qdrant; uploads 3 distinct-topic docs, embeds with GGUFEmbeddings, searches and asserts correct doc ranks first * feat: shared embeddings backend across all vendor surfaces Route the Cohere embed endpoint through the shared embeddings backend and add parity embedding endpoints to Gemini (:embedContent / :batchEmbedContents), HuggingFace TGI (/embed), and AWS Bedrock (Titan/Cohere embed models via /invoke), all delegating to get_embeddings_backend. Anthropic is intentionally left without one (no first-party embeddings API). Also make the merged server actually deployable: - pin sqlalchemy[asyncio] (greenlet) and ovos-persona>=0.9.0a5 - pass a Session to persona.chat/stream via run_chat/run_stream (ovos-persona 0.9 requires it); fixes a 500 on every chat endpoint - adapt vector-store DB instantiation to plugins taking config= or path= - fix an UnboundLocalError that masked RAG-import failures Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test: cross-plugin RAG e2e + deploy-realistic e2e workflow Add test_e2e_rag_solver: the ovos-openai-plugin OpenAIRAGSolver drives a live server (real gguf embeddings + chromadb vector store) through the new /vector_stores/search endpoint, then /chat/completions via the real openai chat solver with only the upstream LLM call mocked. Add .github/workflows/e2e.yml running tests/e2e on the full real stack (install_extras=e2e), pulling the unreleased companions (openai-plugin rag branch, chromadb dev) via pre_install_pip. Update existing tests for the shared backend + Session call contract: cohere/router_infra/a2a unit tests and vendor e2e stream mocks (accept **kwargs). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test: require all e2e deps, remove importorskip guards The [e2e] extra now installs the full union of what tests/e2e imports — every vendor client SDK (openai, ollama, cohere, google-genai, boto3, anthropic, huggingface_hub), mcp, and the embedding/vector-store plugin stack — and all pytest.importorskip guards are removed. A missing dependency now fails the run loudly instead of silently skipping a test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs: RAG/vector-store guide, examples, and embeddings parity Add docs/rag.md (Files + Vector Stores + RAG flow), an examples/ directory (openai-SDK flow, RAGSolver flow, multi-client embeddings, persona config), and update README, docs/index.md, and docs/embeddings.md for the shared embeddings backend and the new per-vendor embed endpoints. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat: model RAG as a persona memory plugin, not a solver Replace the client-side RAGSolver e2e with test_e2e_rag_memory: a real Persona whose memory_module is ovos-openai-rag-memory-plugin (PersonaServerRAGMemory) searches the live server's vector store and injects retrieved context — proving the cross-repo path against gguf + chromadb. Update e2e.yml to install the three unreleased companions (openai-plugin rag, chromadb dev, ovos-persona memory-config). Docs: rewrite the RAG client section around the memory plugin, add the drop-in OpenAI-replacement use case, and refresh examples. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test: cover RAG memory inject_mode=tool in the e2e suite Parametrize the cross-plugin memory e2e over inject_mode (system, tool): the tool case asserts the retrieved context arrives as a MessageRole.TOOL result paired with a matching assistant tool_call. Update the import for the renamed ovos_openai_plugin package, and point e2e.yml's pre_install_pip at the openai opm-agents branch plus the ovos-plugin-manager tool-calling branch. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat: honor tools on /chat/completions instead of silently ignoring The OpenAI chat endpoint accepted `tools`/`tool_choice`/`parallel_tool_calls` and dropped them (echoed into the response, never acted on). Now, when `tools` is sent, the request is routed to the persona's tool-capable chat engine (supports_tools) and any tool_calls are returned (finish_reason="tool_calls") for the client to execute — the server is a stateless function-calling passthrough (it does not run the tools). Incoming assistant tool_calls / tool-role messages are rebuilt as AgentMessages so multi-turn function-calling round-trips. If no tool-capable engine is configured, or stream=true is combined with tools, a 501 is returned rather than silent non-compliance. No behavior change when `tools` is absent. Unit CI installs the ovos-plugin-manager tool-calling branch (chat.py now imports the tool contract) until it releases. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat: server-side CHAT_MEMORY toggle (backend vs hosted-agent) The chat path defaults to a stateless backend (CHAT_MEMORY=off): the client owns conversation state and drives Files/Vector-Stores itself. Set CHAT_MEMORY=transparent for a single-user hosted agent where the server folds the persona's memory_module (history + RAG) into every turn via build_conversation_context + update_history, keyed by the OpenAI `user` field. The toggle lives in run_chat/run_stream so every vendor surface honors it uniformly; tool/function-calling requests stay a stateless passthrough. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(deps): pin released opm>=2.7.0a1 and ovos-persona>=0.9.0a6; trim CI git-refs The tool-call contract (ToolCall, MessageRole.TOOL) is released in ovos-plugin-manager 2.7.0a1 and memory-plugin config passing in ovos-persona 0.9.0a6, so pin both in pyproject and drop their pre_install_pip git-refs: build_tests no longer needs any git-ref; e2e keeps only the still-unreleased ovos-openai-plugin@opm-agents (PR #54) and ovos-chromadb-embeddings-plugin@dev. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(deps): pin released ovos-openai-plugin>=2.0.8a1 in [e2e]; drop its git-ref PersonaServerRAGMemory is published in ovos-openai-plugin 2.0.8a1, so add it to the [e2e] extra and remove the openai git-ref from e2e.yml. Only the still- unreleased ovos-chromadb-embeddings-plugin@dev remains as a pre_install_pip ref. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…p last git-ref (#58) ovos-chromadb-embeddings-plugin 0.3.0a4 (collection-aware EmbeddingsDB API) is now on PyPI, so pin it in the [e2e] extra and remove the final pre_install_pip git-ref from e2e.yml. The persona agent chain is now fully git-ref-free. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
mcp 2.0.0b2 dropped mcp.server.fastmcp.FastMCP, breaking the MCP server import. The unbounded mcp>=1.0.0 pin let uv resolve that prerelease under UV_PRERELEASE=allow, failing every build_tests job.
) `main()` passes `--persona` (default `None`) straight into `create_persona_app()`, which did `with open(persona_path)` unconditionally — so running `ovos-persona-server` with no `--persona` crashed with `TypeError: expected str, bytes or os.PathLike object, not NoneType`. The config docstrings promise the opposite ("If not provided, LLM settings from environment variables are used"), and `Settings.persona_config` already builds exactly that env-var-driven default persona — but it was dead code, wired to nothing. Use it when no persona path is provided, restoring the documented fallback and fixing the crash. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Persona.chat(messages: List[AgentMessage], sess) and Persona.stream(...) declare AgentMessage in their type contract and forward straight to QuestionSolversService.chat_completion()/stream_completion(), which reads messages[-1].content for QuestionSolver-based plugins. run_chat()/run_stream()'s stateless path (CHAT_MEMORY=off, the default) passed the raw OpenAI-style message dicts from the request straight through without converting them first. Any QuestionSolver-based plugin (e.g. ovos-solver-plugin-ddg) then crashed with: AttributeError: 'dict' object has no attribute 'content' which the outer HTTP layer's token-counting code turned into a second, more confusing error further down the stack: AttributeError: 'NoneType' object has no attribute 'split' chat.py already has a _messages_to_agent() converter for the ChatEngine path (continue_chat), it just wasn't reused here. This adds an equivalent local helper in persona.py and applies it at both call sites. Confirmed by constructing AgentMessage/dict objects directly against a real QuestionSolversService instance and reproducing the exact AttributeError with unconverted dicts. Found while packaging this server as a Home Assistant OS Supervisor add-on: https://github.com/andlo/haos-ovos-addons
…through tests (#68) * refactor: unify OpenAI-dict to AgentMessage conversion in persona.py Move _flatten_text/_role/_messages_to_agent from chat.py into persona.py and drop the duplicate _dicts_to_agent_messages, so the stateless run_chat and run_stream paths get the same tool_calls/tool_call_id/name-preserving, function->tool role mapping that chat.py's tool-calling path already had. chat.py now imports the shared helpers instead of redefining them. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test: assert AgentMessage contract on the stateless run_chat/run_stream path The old tests asserted client dicts passed through unconverted; that contract no longer holds now that run_chat/run_stream always convert to AgentMessage. Update them to check the converted objects, and add a regression test covering legacy role mapping, unknown-role fallback, and content-parts flattening for both run_chat and run_stream. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test: update stale passthrough assertions to the AgentMessage contract PR #67 fixed run_chat/run_stream to convert client message dicts to AgentMessage before calling persona.chat/stream, but these tests still asserted the old dict-passthrough contract (subscript access, string role comparison). Assert on the converted AgentMessage objects instead, preserving each test's original intent (role mapping, system-message prepending, content-parts flattening). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test: assert AgentMessage contract in a2a executor forwarding test The a2a async tests only run where pytest-asyncio is active, so this stale dict-passthrough assertion was missed by the local sweep and only surfaced in CI. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Human review requested!