{"id":51627940,"url":"https://github.com/bima42/jstream-py","last_synced_at":"2026-07-13T03:01:34.245Z","repository":{"id":356779198,"uuid":"1233985946","full_name":"Bima42/jstream-py","owner":"Bima42","description":"Turn a raw LLM token stream into validated JSON objects as fields complete","archived":false,"fork":false,"pushed_at":"2026-05-09T17:07:15.000Z","size":27,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-09T19:05:58.772Z","etag":null,"topics":["json","pydantic-v2","python","uv"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Bima42.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-09T15:48:53.000Z","updated_at":"2026-05-09T17:07:11.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/Bima42/jstream-py","commit_stats":null,"previous_names":["bima42/jstream-py"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/Bima42/jstream-py","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Bima42%2Fjstream-py","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Bima42%2Fjstream-py/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Bima42%2Fjstream-py/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Bima42%2Fjstream-py/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Bima42","download_url":"https://codeload.github.com/Bima42/jstream-py/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Bima42%2Fjstream-py/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35408466,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-13T02:00:06.543Z","response_time":119,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["json","pydantic-v2","python","uv"],"created_at":"2026-07-13T03:01:33.015Z","updated_at":"2026-07-13T03:01:34.231Z","avatar_url":"https://github.com/Bima42.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# jstream-py\n\nTurn a raw LLM token structured output stream into validated objects as fields complete.\n\n```\n\"{\"                                  → (nothing yet)\n'{\"title\":\"Inception\",'              → {\"title\": \"Inception\"}\n'{\"title\":\"Inception\",\"year\":2010}'  → {\"title\": \"Inception\", \"year\": 2010}\n```\n\n## Install\n\n```bash\npip install jstream-py\n```\n\n## Usage\n\n### Without a schema — yields plain dicts\n\n```python\nfrom jstream import parse_stream\n\nasync for partial in parse_stream(token_stream):\n    print(partial)  # {\"title\": \"Inception\"}, then {\"title\": \"Inception\", \"year\": 2010}, ...\n```\n\n### With a Pydantic schema — yields model instances\n\n```python\nfrom pydantic import BaseModel\nfrom jstream import parse_stream\n\nclass Film(BaseModel):\n    title: str = \"\"\n    year: int = 0\n    rating: float = 0.0\n\nasync for film in parse_stream(token_stream, schema=Film):\n    print(film.title)  # populated as soon as \"title\" field closes\n```\n\nIncomplete fields receive model defaults. Extra keys are ignored by Pydantic, preserved in plain-dict mode.\n\n### With the OpenAI SDK (and OpenRouter)\n\n`delta.content` can be `None` on the first and last chunks — `jstream-py` skips them automatically.\n\n```python\nfrom openai import AsyncOpenAI\nfrom jstream import parse_stream\n\nclient = AsyncOpenAI(base_url=\"https://openrouter.ai/api/v1\", api_key=\"...\")\n\nasync def token_stream(response):\n    async for chunk in response:\n        yield chunk.choices[0].delta.content  # None chunks are skipped\n\nresponse = await client.chat.completions.create(\n    model=\"openai/gpt-4o-mini\",\n    messages=[{\"role\": \"user\", \"content\": \"...\"}],\n    response_format={\"type\": \"json_schema\", \"json_schema\": {\"name\": \"film\", \"strict\": True, \"schema\": schema}},\n    stream=True,\n)\n\nasync for film in parse_stream(token_stream(response), schema=Film):\n    print(film.title)\n```\n\n## API\n\n```python\nasync def parse_stream(\n    stream: AsyncIterator[str],\n    schema: type[BaseModel] | None = None,\n) -\u003e AsyncIterator[dict | BaseModel]:\n```\n\n`stream` — any async iterator of raw string chunks. Chunks need not align to field boundaries.\n\n`schema` — optional Pydantic model. When provided, yields model instances; raises `JstreamValidationError` after the stream closes if the complete JSON fails validation.\n\n## Behavior\n\n| Situation                                       | Behavior                              |\n| ----------------------------------------------- | ------------------------------------- |\n| Chunk arrives mid-field                         | Silent — no yield until field closes  |\n| Chunk produces no new completed fields          | No yield (deduplicated)               |\n| Stream closes on valid JSON                     | No error, even with schema            |\n| Stream closes on invalid JSON (schema provided) | Raises `JstreamValidationError`       |\n| Stream closes on invalid JSON (no schema)       | No error                              |\n| Whitespace-only chunks                          | Skipped                               |\n| `None` chunks                                   | Skipped (safe with OpenAI SDK deltas) |\n\n## Error Handling\n\n`JstreamValidationError` is raised after the stream is fully consumed, never mid-stream. Partial JSON during streaming is always silent.\n\n```python\nfrom jstream import parse_stream, JstreamValidationError\n\ntry:\n    async for item in parse_stream(token_stream, schema=Film):\n        ...\nexcept JstreamValidationError as e:\n    print(e.errors)  # Pydantic error list\n    print(e.raw)     # the complete accumulated string\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbima42%2Fjstream-py","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbima42%2Fjstream-py","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbima42%2Fjstream-py/lists"}