0% found this document useful (0 votes)
14 views37 pages

Python Asyncio Blocking vs Non-Blocking

The document discusses various Python programming techniques, focusing on asynchronous programming with asyncio, blocking vs non-blocking function calls, and handling data types like numpy arrays and pandas DataFrames. It includes code examples demonstrating how to maintain responsiveness in event loops and how to prepare data for GraphML representation. Additionally, it touches on JSON serialization issues related to NumPy types and provides insights into implementing traceability in data processing.

Uploaded by

trumpeter123
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
14 views37 pages

Python Asyncio Blocking vs Non-Blocking

The document discusses various Python programming techniques, focusing on asynchronous programming with asyncio, blocking vs non-blocking function calls, and handling data types like numpy arrays and pandas DataFrames. It includes code examples demonstrating how to maintain responsiveness in event loops and how to prepare data for GraphML representation. Additionally, it touches on JSON serialization issues related to NumPy types and provides insights into implementing traceability in data processing.

Uploaded by

trumpeter123
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd

import re

nums = [int([Link](1)) for f in sub_batch_files

if (m := [Link](r"sub_batch_(\d+)\.jsonl$", f))]

max_index = max(nums) # or max(nums, default=1)

[Link](r"sub_batch_(\d+)\.jsonl$", f) tries to match this pattern in each


filename f:

sub_batch_ — literal text.

(\d+) — one or more digits, captured as group 1. (\d = digit, + = one or


more.)

\.jsonl — literal .jsonl (the dot is escaped so it means a real dot, not “any
char”).

$ — end of the string anchor (ensures the filename ends with .jsonl and
nothing extra after).

# blocking_demo.py

import asyncio

import time

def slow_function(x: int) -> int:

# Simulate heavy/blocking work (CPU or a blocking I/O/lib call)

[Link](2) # Blocks the OS thread

return x * 2
async def ticker():

# Shows whether the event loop is alive

i=0

while True:

print(f"[ticker] alive {i}")

i += 1

await [Link](0.2)

async def main():

# Start a background ticker

tick_task = asyncio.create_task(ticker())

print("[main] starting 5 blocking calls (this will FREEZE the ticker)")

results = []

for i in range(5):

# ❌ This is synchronous and blocks the event loop thread

r = slow_function(i)

[Link](r)

print(f"[main] blocking result for {i} -> {r}")

tick_task.cancel()

try:

await tick_task

except [Link]:

pass

print("[main] done, results:", results)


if __name__ == "__main__":

[Link](main())

What you’ll see

The ticker prints a couple of lines like [ticker] alive 0, [ticker] alive 1 … then
suddenly stops for ~10 seconds (5 calls × 2s each).

After all blocking work finishes, the ticker may resume briefly or immediately
cancel when main ends.

Non-blocking version (event loop stays responsive)

# nonblocking_demo.py

import asyncio

import time

from typing import List

def slow_function(x: int) -> int:

[Link](2) # Same blocking call as before

return x * 2

async def ticker():

i=0

while True:

print(f"[ticker] alive {i}")

i += 1
await [Link](0.2)

async def compute_many(nums: List[int]):

# Kick off the slow work in threads WITHOUT blocking the event loop

# You can run them sequentially with to_thread...

# results = []

# for n in nums:

# [Link](await asyncio.to_thread(slow_function, n))

# return results

# ...or concurrently with gather:

tasks = [asyncio.to_thread(slow_function, n) for n in nums]

return await [Link](*tasks)

async def main():

tick_task = asyncio.create_task(ticker())

print("[main] starting 10 non-blocking calls via asyncio.to_thread")

results = await compute_many(list(range(10)))

print("[main] done, results:", results)

tick_task.cancel()

try:

await tick_task

except [Link]:

pass
if __name__ == "__main__":

[Link](main())

What you’ll see

The ticker never stops; you keep seeing [ticker] alive N every ~0.2s while the
work runs.

Because we used asyncio.to_thread, the blocking [Link](2) happens in


worker threads, so the event loop can keep scheduling other coroutines (like
ticker).

Bonus: Throttled concurrency (limit threads)

If you want many tasks but don’t want to spawn too many threads at once:

# throttled_nonblocking_demo.py

import asyncio

import time

from typing import Iterable

def slow_function(x: int) -> int:

[Link](2)

return x * 2

async def ticker():

i=0

while True:

print(f"[ticker] alive {i}")


i += 1

await [Link](0.2)

async def bounded_to_thread(fn, arg, sem: [Link]):

async with sem:

return await asyncio.to_thread(fn, arg)

async def main():

tick_task = asyncio.create_task(ticker())

nums = list(range(30))

sem = [Link](5) # at most 5 blocking jobs running


concurrently

tasks = [bounded_to_thread(slow_function, n, sem) for n in nums]

results = await [Link](*tasks)

print("[main] done, results (len):", len(results))

tick_task.cancel()

try:

await tick_task

except [Link]:

pass

if __name__ == "__main__":

[Link](main())
Add a special case for [Link] and convert it via .tolist() before
normalizing each item:

if isinstance(raw, [Link]):

return [str(item).strip() for item in [Link]() if str(item).strip()]

Here’s the gist on the double-underscore (“dunder”) names:

 What “dunder” means

 Names wrapped in double underscores (like __init__) are


“special” to Python. The interpreter looks for these exact names
to implement built-in behaviors (operators, construction,
iteration, context managers, etc.).

 __init__ vs __init__.py

 __init__(self, …) is a special method on classes. Python calls it


right after creating an instance (the initializer; the actual
constructor is __new__).

 __init__.py is a package initializer file. When you import a


package, Python executes this file to set up the package
namespace (commonly where packages set __version__). With
PEP 420, it’s no longer required for “namespace packages”, but
most normal packages still include it.

 __version__

 Not enforced by the interpreter; it’s a widely adopted convention


for packages/modules to expose their version string.

 Where it lives: often set in the top-level package __init__.py,


e.g., __version__ = "1.2.3".

 Canonical way to get installed distribution versions


is [Link]("package-name"), which reads
package metadata without importing the package.

 Why double underscores?

 It signals “this name has special semantics” and reduces


collisions with ordinary attribute names. The interpreter and
standard tooling check for these exact names
(e.g., __len__, __iter__, __enter__/__exit__, __add__, __repr__, __get
attr__, etc.).

 Don’t confuse with name mangling

 A name with only a double leading underscore (e.g., __private)


inside a class triggers name-mangling to _ClassName__private.
That’s different from special dunders which have both leading
and trailing underscores (e.g., __init__, __version__) and are not
mangled.

 Markdown “bolding”

 Some UIs render __name__ as bold because Markdown treats


double underscores as bold markers. In a Python shell it won’t
bold; it’s just a display artifact.

 Practical tips

 Show versions safely:

 python -c "import [Link] as m;


print([Link]('lancedb'), [Link]('pyarrow'))"

 or python -c "import lancedb, pyarrow;


print(lancedb.__version__, pyarrow.__version__)" (imports
the packages, which can have side effects)

 Common module dunders you’ll


see: __name__, __file__, __package__, __doc__, __all__, __spec__.

Using pandas at my finalize_graph.py so it prepares node


metadata and flattened attributes, DataFrame, flatten
arrays/lists/dicts

# Copyright (c) 2024 Microsoft Corporation.

# Licensed under the MIT License

"""A module containing run_workflow method definition."""


import logging

from typing import Any

import pandas as pd

from [Link].embed_graph_config import EmbedGraphConfig

from [Link].graph_rag_config import GraphRagConfig

from [Link].create_graph import create_graph

from [Link].finalize_entities import finalize_entities

from [Link].finalize_relationships import finalize_relationships

from [Link].snapshot_graphml import snapshot_graphml

from [Link] import PipelineRunContext

from [Link] import WorkflowFunctionOutput

from [Link] import load_table_from_storage, write_table_to_storage

logger = [Link](__name__)

def _flatten_value(value: Any) -> Any:

"""Convert complex values (lists, dicts, ndarrays) to GraphML-friendly


scalars/strings."""

try:

import numpy as np # local import to avoid hard dependency at module


import time

except Exception:

np = None

# Pass through simple scalars

if value is None or isinstance(value, (str, int, float, bool)):

return value
# numpy types

if np is not None:

if isinstance(value, ([Link],)):

return [Link]()

if isinstance(value, ([Link],)):

try:

# Flatten small arrays sensibly

return ", ".join(str(x) for x in [Link]().tolist())

except Exception:

return str(value)

# lists/tuples

if isinstance(value, (list, tuple)):

try:

return ", ".join(str(v) for v in value)

except Exception:

return str(value)

# dicts

if isinstance(value, dict):

try:

# Compact JSON-like representation

return ", ".join(f"{k}={v}" for k, v in [Link]())

except Exception:

return str(value)

# Fallback
return str(value)

def _prepare_entities_for_graphml(entities: [Link]) -> [Link]:

"""Return a copy of entities with flattened, GraphML-friendly columns.

- Ensures a 'text' column (alias of 'description') for downstream matching

- Flattens list/array/dict-like columns to strings

- Keeps core descriptive fields readable

"""

if entities is None or len(entities) == 0:

return entities

df = [Link]()

# Provide a 'text' alias to help downstream consumers that expect [Link]

if "text" not in [Link]:

if "description" in [Link]:

df["text"] = df["description"].fillna("")

else:

df["text"] = ""

# Flatten all columns to GraphML-friendly scalars

for col in [Link]:

df[col] = df[col].apply(_flatten_value)

return df

async def run_workflow(


config: GraphRagConfig,

context: PipelineRunContext,

) -> WorkflowFunctionOutput:

"""All the steps to create the base entity graph."""

[Link]("Workflow started: finalize_graph")

entities = await load_table_from_storage("entities", context.output_storage)

relationships = await load_table_from_storage(

"relationships", context.output_storage

final_entities, final_relationships = finalize_graph(

entities,

relationships,

embed_config=config.embed_graph,

layout_enabled=[Link],

await write_table_to_storage(final_entities, "entities", context.output_storage)

await write_table_to_storage(

final_relationships, "relationships", context.output_storage

if [Link]:

# Build a graph that includes node attributes and useful edge metadata.

nodes_df = _prepare_entities_for_graphml(final_entities)

# Keep common edge attrs (weight + description if present)

edge_attrs: list[str] = ["weight"]


if "description" in final_relationships.columns:

edge_attrs.append("description")

graph = create_graph(

final_relationships,

edge_attr=edge_attrs,

nodes=nodes_df,

node_id="title",

# Log a concise snapshot summary for easier debugging downstream

try:

[Link](

"Snapshotting GraphML: nodes=%d, edges=%d, edge_attrs=%s",

len(nodes_df) if nodes_df is not None else 0,

len(final_relationships),

",".join(edge_attrs),

except Exception:

pass

await snapshot_graphml(

graph,

name="graph",

storage=context.output_storage,

[Link]("Workflow completed: finalize_graph")


return WorkflowFunctionOutput(

result={

"entities": entities,

"relationships": relationships,

def finalize_graph(

entities: [Link],

relationships: [Link],

embed_config: EmbedGraphConfig | None = None,

layout_enabled: bool = False,

) -> tuple[[Link], [Link]]:

"""All the steps to finalize the entity and relationship formats."""

final_entities = finalize_entities(

entities, relationships, embed_config, layout_enabled

final_relationships = finalize_relationships(relationships)

return (final_entities, final_relationships)

Project IMPLEMENTING TRACEABILITY MENTIS

+from bisect import bisect_right

 O(log L) line lookup. With bisect_right(self._line_starts, pos), you map any char
offset to a 1-based line number quickly. This is a canonical trick in text editors and
compilers.

implementing traceability

 No lossy tokenization. Using \S+ leaves whitespace intact in the original


buffer, which is crucial for slicing true spans later.

b) Chunk construction: _create_chunks(...)

 For spec text, chunks are created by slicing the original text using
word_spans[i:j] bounds—not by ' '.join(words)—so each chunk is an exact
substring of the persisted spec_text.txt.

 For each chunk i, it records:

o self.chunk_spans_chars[i] = (start_char, end_char)

o self.chunk_spans_lines[i] = (start_line, end_line)

 It also includes a fallback (no spans) when word spans aren’t available.

  Referential integrity. By slicing text[start:end], you guarantee


text[start:end] == chunks[i]. This is the single most important invariant for
provenance.
  Overlap safety. The step uses max(1, chunk_size - overlap) to avoid
zero/negative stepping (a classic off-by-one pitfall).

Retrieval: retrieve(query, k) returns (chunk_index, chunk_text)

 A TF-IDF vectorizer is fit on [Link]. Given a query, it computes cosine


similarities and returns the top-k results as pairs (i, [Link][i]).

 Returning the index is key: it ties straight back to chunk_spans_chars[i] and


chunk_spans_lines[i].

+ results = [Link](query, k=k)

+ for i, (chunk_idx, chunk) in enumerate(results):

# Score based on rank and chunk properties

@@ -145,2 +145,23 @@

+ # Build metadata including provenance

+ meta = {

+ 'chunk_size': retriever.chunk_size,
+ 'overlap': [Link],

+ 'rank': i + 1,

+ 'content_type': 'spec',

+ 'chunk_index': chunk_idx,

+ }

+ if (

+ hasattr(retriever, 'chunk_spans_chars') and

+ retriever.chunk_spans_chars and 0 <= chunk_idx <


len(retriever.chunk_spans_chars)

+ ):

+ start_char, end_char =
retriever.chunk_spans_chars[chunk_idx]

+ meta['span_chars'] = [start_char, end_char]

+ if (

+ hasattr(retriever, 'chunk_spans_lines') and

+ retriever.chunk_spans_lines and 0 <= chunk_idx <


len(retriever.chunk_spans_lines)

+ ):

+ start_line, end_line =
retriever.chunk_spans_lines[chunk_idx]

+ meta['span_lines'] = [start_line, end_line]

🧠 Background — why JSON serialization fails

When you use:

top_k_indices = [Link]()[-k:][::-1]

argsort() from NumPy returns a [Link] containing NumPy scalar types,


e.g.:

>>> type(top_k_indices[0])
<class 'numpy.int64'>

The problem is that numpy.int64 is not JSON-serializable by Python’s built-in


json module.

So if downstream code tries something like:

[Link]({"chunk_index": top_k_indices[0]}, f)

you get:

TypeError: Object of type int64 is not JSON serializable

That’s what this patch is fixing.

🧩 The change explained

Original

return [(i, [Link][i]) for i in top_k_indices]

 i here is a NumPy integer (e.g. numpy.int64).

 Works fine for arithmetic.

 Fails later if this tuple is ever passed into something like [Link](...), or
stored inside [Link] that will be serialized.

Fixed version

# Return (index, chunk_text) for provenance with Python int indices

# Ensure we don't leak numpy.int64 into downstream JSON

try:

idx_list = top_k_indices.tolist()

except Exception:

idx_list = list(top_k_indices)

return [(int(i), [Link][int(i)]) for i in idx_list]

Step by step:

1. top_k_indices.tolist()

o Converts the NumPy array into a regular Python list of int values.

o Example:
o [Link]([1,2,3], dtype=np.int64).tolist() # → [1, 2, 3]

o If for any reason top_k_indices is not a NumPy array (already a list or


something else), .tolist() might fail → hence the try/except.

2. list(top_k_indices) (fallback)

o Works for most iterable types.

3. int(i)

o Explicitly casts every index into a native Python int.

o Guarantees that downstream code, including JSON serialization,


handles these values safely.

4. Final return:

5. return [(int(i), [Link][int(i)]) for i in idx_list]

o Each element is now a tuple (int, str).

o Both types are natively serializable by [Link]().

🔍 Example of what it prevents

Before:

top_k_indices = [Link]([42, 43])

results = [(top_k_indices[0], "some text")]

[Link](results)

→ ❌ TypeError: Object of type int64 is not JSON serializable

After:

results = [(int(top_k_indices[0]), "some text")]

[Link](results)

→ ✅ Works perfectly.

⚙️Python principle behind this

🔹 Numpy types vs built-in types

NumPy defines its own scalar classes (numpy.int64, numpy.float32, etc.),


which mimic Python’s built-in int and float but have C-level dtypes and are not
part of the standard JSON encoder.
🔹 JSON encoding in Python

[Link]() (and [Link]()) use the default [Link], which supports:

 built-in Python types (int, float, str, list, dict, bool, None)

 not 3rd-party numeric types.

Hence, always cast to native types before serialization.

✅ Why the fix is good practice

Problem Fix Benefit

NumPy scalar not JSON Convert via .tolist() or


Ensures stable JSON
serializable int()

Downstream context
Explicit Python int Prevents pipeline crash
metadata fails

Portability issues (different Works across OS/Python


Explicit conversion
dtypes) versions

# src/indexing/protocol_index.py

import re

from dataclasses import dataclass

from typing import List, Dict

@dataclass

class ProtocolPara:

id: str

text: str

tokens_hex: List[str]

tokens_words: List[str]

timing_mentions: List[str]
numbers: List[int] # plain digits found

class ProtocolIndex:

HEX_RE = [Link](r"0x[0-9A-Fa-f]+")

DIGITS_RE = [Link](r"\b\d+\b")

TIMING_WORDS = {"cycle", "clock", "latency", "period", "baud", "MHz",


"kHz"}

SPECIAL_WORDS = {"ACK", "CR", "LF"}

def __init__(self, paragraphs: List[str], prefix: str = "txt:protocol"):

[Link]: List[ProtocolPara] = []

for i, txt in enumerate(paragraphs, start=1):

pid = f"{prefix}:para{i}"

hexes = self.HEX_RE.findall(txt)

nums = list(map(int, self.DIGITS_RE.findall(txt)))

timing = [w for w in self.TIMING_WORDS if [Link](rf"\b{w}\b", txt,


re.I)]

specials = [w for w in self.SPECIAL_WORDS if [Link](rf"\b{w}\b",


txt, re.I)]

[Link](ProtocolPara(pid, txt, hexes, specials, timing,


nums))

def query(self, terms: List[str]) -> List[ProtocolPara]:

q = " ".join(terms)

return [p for p in [Link] if all([Link]() in [Link]() for t in terms)]

def as_evidence(self) -> List[Dict]:

return [p.__dict__ for p in [Link]]


Layout

config/

[Link]

src/

gen_plan.py # Orchestrator

indexing/

protocol_index.py

spec_index.py # stubbed for widths & spans

retriever/

[Link] # EvidencePack (per-plan)

generation/

generator_llm.py # Single-LLM client + candidates

validation/

grounding_validator.py # Python E-rules

rewriter/

[Link] # Surgical fixes

ranking/

[Link]

[Link] # CLI entry

config/[Link]

[multi_agent]

enabled = true

draft_candidates = 5

rank_top_k = 5
[validator]

forbid_performance_claims = true

enforce_width_ranges = true

[rewriter]

inject_missing_triggers = true

placeholder_timing = "(unspecified)"

[runtime]

workers = 4

[llm]

model = "gpt-4o-mini"

temperature_generator = 0.5

temperature_rewriter = 0.1

src/indexing/protocol_index.py

import re

from dataclasses import dataclass

from typing import List, Dict

@dataclass

class ProtocolPara:

id: str

text: str

numbers: List[int]

tokens_hex: List[str]
timing_mentions: List[str]

class ProtocolIndex:

HEX_RE = [Link](r"0x[0-9A-Fa-f]+")

DIGITS_RE = [Link](r"\b\d+\b")

TIMING_WORDS =
{"cycle","clock","latency","period","baud","MHz","kHz","next"}

def __init__(self, paragraphs: List[str], prefix="txt:protocol"):

[Link]: List[ProtocolPara] = []

for i, txt in enumerate(paragraphs, 1):

pid = f"{prefix}:para{i}"

hexes = self.HEX_RE.findall(txt)

nums = list(map(int, self.DIGITS_RE.findall(txt)))

timing = [w for w in self.TIMING_WORDS if [Link](rf"\b{w}\b", txt,


re.I)]

[Link](ProtocolPara(pid, txt, nums, hexes, timing))

def query(self, terms: List[str]) -> List[ProtocolPara]:

return [p for p in [Link] if all([Link]() in [Link]() for t in terms)]

def as_evidence(self) -> List[Dict]:

return [p.__dict__ for p in [Link]]

src/indexing/spec_index.py (stub)

from typing import Dict, List


class SpecIndex:

def __init__(self, signal_rows: Dict[str, str], signal_widths: Dict[str, int]):

[Link] = signal_rows # e.g., {"int_rd_data": "row text ..."}

[Link] = signal_widths # e.g., {"int_rd_data": 8, "int_address":


16}

def lookup_signal_spans(self, signal: str):

# ultra-minimal numbers extractor

text = [Link](signal, "")

nums = [int(t) for t in [Link]() if [Link]()]

return {"ids": [f"tbl:signals:{signal}"], "numbers": nums}

def widths_for_referenced(self, signals: List[str]) -> Dict[str, int]:

return {s: [Link](s) for s in signals if s in [Link]}

def signal_dict(self) -> List[str]:

return list([Link]())

src/retriever/[Link] (per-plan EvidencePack)

from typing import Dict, List

from ..indexing.protocol_index import ProtocolIndex

class Retriever:

def __init__(self, spec_index, protocol_index: ProtocolIndex):

[Link] = spec_index

[Link] = protocol_index
def build_pack(self, signal: str, referenced_signals: List[str],
protocol_terms: List[str]):

spans = [Link].lookup_signal_spans(signal)

proto = [Link](protocol_terms) if protocol_terms else []

allowed_numbers = sorted(set(

spans["numbers"] + [n for p in proto for n in [Link]]

))

evidence_span_ids = spans["ids"] + [[Link] for p in proto]

widths = [Link].widths_for_referenced(referenced_signals)

return {

"signal": signal,

"allowed_numbers": allowed_numbers,

"evidence_span_ids": evidence_span_ids,

"widths": widths,

"signal_dict": [Link].signal_dict()

src/generation/generator_llm.py

from typing import List, Dict, Any, Optional

import json

GEN_SYS = """You are a hardware spec planner.

Output STRICT JSON only, matching this schema:

"signal": str,

"trigger": str,

"effects": [str, ...],


"timing": "(unspecified)" | int,

"ranges": { signal: { "min": int, "max": int } | "(unspecified)" },

"evidence_span_ids": [str, ...]

Rules:

- Use ONLY numbers contained in allowed_numbers or implied by bit widths.

- If unknown, write "(unspecified)" (do NOT invent).

- Do NOT claim performance (MHz/baud) unless evidence explicitly mentions


it (with numbers).

- Prefer given relations (if any).

Return JSON ONLY.

"""

GEN_USER_TMPL = """TASK: Draft {k} JSON plan candidates for signal:


{signal}

CONTEXT:

- allowed_numbers: {allowed_numbers}

- widths: {widths}

- evidence_span_ids: {evidence_span_ids}

HINTS:

- If mentioning int_rd_data, ensure it is produced by int_read.

- Next-cycle timing only if evidence mentions it.

"""

class SingleLLMClient:

def __init__(self, model: str, temperature: float = 0.5, call_fn=None):

[Link] = model
[Link] = temperature

# call_fn(system_prompt, user_prompt, temperature) -> str

self.call_fn = call_fn or self._fake_call

def _fake_call(self, system_prompt: str, user_prompt: str, temperature:


float) -> str:

# Minimal deterministic mock

plan = {

"signal": "int_read",

"trigger": "int_read",

"effects": ["int_rd_data"],

"timing": "(unspecified)",

"ranges": {"int_rd_data": {"min": 0, "max": 255}},

"evidence_span_ids": ["tbl:signals:int_read"]

return [Link]([plan]) # return a JSON array of plans

def generate_candidates(self, signal: str, evidence_pack: Dict[str, Any],


k=5) -> List[Dict[str, Any]]:

user = GEN_USER_TMPL.format(

k=k,

signal=signal,

allowed_numbers=evidence_pack["allowed_numbers"],

widths=evidence_pack["widths"],

evidence_span_ids=evidence_pack["evidence_span_ids"],

raw = self.call_fn(GEN_SYS, user, [Link])


# Expect either single or list

try:

data = [Link](raw)

if isinstance(data, dict): data = [data]

return data[:k]

except Exception:

return []

Plug your actual OpenAI call where _fake_call is (keeping system_prompt,


user_prompt, temperature).

src/validation/grounding_validator.py (Python E-rules)

from typing import Dict, Any, List, Tuple

class GroundingValidator:

def __init__(self, policy: Dict[str, Any]):

[Link] = policy

def _width_bounds(self, w: int) -> Tuple[int,int]:

return (0, (1<<w)-1)

def _within_width(self, num: int, widths: Dict[str,int]) -> bool:

for _, w in [Link]():

lo, hi = self._width_bounds(w)

if lo <= num <= hi:

return True

return False
def _collect_numbers(self, plan: Dict[str, Any]) -> List[int]:

nums = []

# timing can be int

if isinstance([Link]("timing"), int): [Link](plan["timing"])

# ranges min/max

for v in ([Link]("ranges") or {}).values():

if isinstance(v, dict):

if isinstance([Link]("min"), int): [Link](v["min"])

if isinstance([Link]("max"), int): [Link](v["max"])

return nums

def validate(self, plan: Dict[str, Any], evidence: Dict[str, Any]) -> Dict[str,
Any]:

flags: List[str] = []

allowed = set([Link]("allowed_numbers", []))

widths = [Link]("widths", {})

sigdict = set([Link]("signal_dict", []))

# E1: evidence required

if not [Link]("evidence_span_ids"):

[Link]("E1: missing evidence_span_ids")

# E3: signals must exist

for s in [[Link]("signal"), [Link]("trigger")] + [Link]("effects", []):

if s and s not in sigdict: [Link](f"E3: unknown signal {s}")

# E2: numeric purity


for n in self._collect_numbers(plan):

if (n not in allowed) and (not self._within_width(n, widths)):

[Link](f"E2: number {n} not allowed")

# E4: performance ban (very simple textual guard)

if [Link]("forbid_performance_claims", True):

joined = str(plan)

if any(w in joined for w in ["MHz","kHz","throughput","baud"]):

if not any("MHz" in e for e in [Link]("evidence_span_ids", [])):

[Link]("E4: performance claim without cited evidence")

# E5: next-cycle timing only with evidence (simple proxy rule)

if [Link]("timing") == 1:

if not any("next" in eid or "int_rd_data" in eid for eid in


[Link]("evidence_span_ids", [])):

[Link]("E5: next-cycle timing lacks evidence")

status = "ok" if not flags else "flagged"

return {"status": status, "plan": plan, "flags": flags}

src/rewriter/[Link]

from typing import Dict, Any, List

class PlanRewriter:

def __init__(self, placeholder_timing="(unspecified)",


inject_missing_triggers=True):

self.placeholder_timing = placeholder_timing
self.inject_missing_triggers = inject_missing_triggers

def _timing_demote(self, plan: Dict[str, Any]) -> None:

plan["timing"] = self.placeholder_timing

def _inject_trigger_for_rd(self, plan: Dict[str, Any]) -> None:

if "int_rd_data" in [Link]("effects", []) and [Link]("trigger") !=


"int_read":

plan["trigger"] = "int_read"

def _normalize_ranges(self, plan: Dict[str, Any], widths: Dict[str,int]) ->


None:

ranges = [Link]("ranges", {})

for sig, w in [Link]():

if sig in ranges:

ranges[sig] = {"min": 0, "max": (1<<w)-1}

def rewrite(self, validator_result: Dict[str, Any], evidence: Dict[str, Any]) ->


Dict[str, Any]:

plan = dict(validator_result["plan"])

flags = validator_result["flags"]

if any([Link]("E5") for f in flags): self._timing_demote(plan)

if self.inject_missing_triggers: self._inject_trigger_for_rd(plan)

if any([Link]("E2") for f in flags): self._normalize_ranges(plan,


[Link]("widths", {}))

# scrub perf words


joined = str(plan)

if any(w in joined for w in ["MHz","kHz","throughput","baud"]):

# simplest scrub: demote timing text and remove perf-y fields if


present

plan["timing"] = self.placeholder_timing

# ensure evidence list exists

[Link]("evidence_span_ids", [Link]("evidence_span_ids",
[]))

return plan

src/ranking/[Link]

from typing import List, Dict, Any

def select(plans: List[Dict[str, Any]], top_k: int = 5) -> List[Dict[str, Any]]:

# naive diversity: prefer different timing and effects shapes

key = lambda p: ([Link]("timing"), tuple(sorted([Link]("effects", []))))

uniq = {}

for p in plans:

[Link](key(p), p)

if len(uniq) >= top_k: break

return list([Link]())[:top_k]

src/gen_plan.py (orchestrator)

from typing import List, Dict, Any

from .[Link] import Retriever

from .generation.generator_llm import SingleLLMClient


from .validation.grounding_validator import GroundingValidator

from .[Link] import PlanRewriter

from .ranking import ranker

class Pipeline:

def __init__(self, retriever: Retriever, llm: SingleLLMClient, cfg: Dict[str,


Any]):

[Link] = retriever

[Link] = llm

[Link] = cfg

[Link] = GroundingValidator(policy={

"forbid_performance_claims": [Link]["validator"]
["forbid_performance_claims"]

})

[Link] = PlanRewriter(

placeholder_timing=[Link]["rewriter"]["placeholder_timing"],

inject_missing_triggers=[Link]["rewriter"]["inject_missing_triggers"]

def process_signal(self, signal: str, referenced_signals: List[str],


protocol_terms: List[str]) -> List[Dict[str, Any]]:

# 1) Build per-plan evidence

pack = [Link].build_pack(signal, referenced_signals,


protocol_terms)

# 2) Generate k JSON candidates

k = [Link]["multi_agent"]["draft_candidates"]

drafts = [Link].generate_candidates(signal, pack, k=k)


# 3) Validate deterministically

judged = [[Link](d, pack) for d in drafts]

ok = [j["plan"] for j in judged if j["status"] == "ok"]

flagged = [j for j in judged if j["status"] == "flagged"]

# 4) Rewrite flagged → repair

repaired = [[Link](j, pack) for j in flagged]

# 5) Re-validate repaired

repaired_ok = [[Link](p, pack)["plan"] for p in repaired

if [Link](p, pack)["status"] == "ok"]

# 6) Rank & select

finalists = [Link](ok + repaired_ok, top_k=[Link]["multi_agent"]


["rank_top_k"])

# 7) Guarantee coverage (optional top-up)

if len(finalists) < [Link]["multi_agent"]["rank_top_k"]:

# schema-only retry could be added here (not shown for brevity)

pass

return finalists

[Link] (wire it up)

import toml

from [Link].spec_index import SpecIndex


from [Link].protocol_index import ProtocolIndex

from [Link] import Retriever

from [Link].generator_llm import SingleLLMClient

from src.gen_plan import Pipeline

def openai_call(system_prompt, user_prompt, temperature):

# Replace with your real OpenAI call (pseudo):

# client = OpenAI()

# resp = [Link](

# model="gpt-4o-mini",

# messages=[{"role":"system","content":system_prompt},

# {"role":"user","content":user_prompt}],

# temperature=temperature,

#)

# return [Link][0].[Link]

raise NotImplementedError("Wire your OpenAI call here")

def load_cfg(path="config/[Link]"):

return [Link](path)

if __name__ == "__main__":

cfg = load_cfg()

# Minimal demo indices

signal_rows = {

"int_read": "Read strobe; next cycle data valid.",

"int_rd_data": "8-bit data output.",


"int_write": "Write strobe; same cycle data accept.",

"int_wr_data": "8-bit data input.",

"int_address": "16-bit address bus."

widths = {"int_rd_data": 8, "int_wr_data": 8, "int_address": 16}

spec = SpecIndex(signal_rows, widths)

protocol = ProtocolIndex([

"Controller acknowledges with 0x5A and may respond next cycle.",

"CR LF terminators for text mode."

])

retriever = Retriever(spec, protocol)

llm = SingleLLMClient(

model=cfg["llm"]["model"],

temperature=cfg["llm"]["temperature_generator"],

call_fn=openai_call # ← plug your real call

pipe = Pipeline(retriever, llm, cfg)

# Example run per signal

results = pipe.process_signal(

signal="int_read",

referenced_signals=["int_rd_data", "int_address"],

protocol_terms=["next","cycle"]

)
print("FINAL PLANS:")

for i, p in enumerate(results, 1):

print(f"[{i}] {p}")

You might also like