Spaces:
Paused
Paused
Download engine/graph.py from Tasosh/barter-agentic-mcp: direct link, hf CLI and curl.
- Browser
- Download file 5.2 kB
-
https://huggingface.co/spaces/Tasosh/barter-agentic-mcp/resolve/main/engine/graph.py
- Command line
-
hf download hf://spaces/Tasosh/barter-agentic-mcp/engine/graph.py
-
curl -L -o graph.py https://huggingface.co/spaces/Tasosh/barter-agentic-mcp/resolve/main/engine/graph.py
5.2 kB
| """ | |
| The matching engine. NOT an LLM. | |
| Pipeline: | |
| 1. Embeddings (TF-IDF here, Bedrock Titan/Cohere in prod) -> semantic | |
| similarity between every WANT and every other member's OFFER. | |
| 2. Hard constraint filter (radius, availability overlap, hard_nos) -- | |
| this runs BEFORE scoring; an LLM never sees a candidate that fails | |
| a hard constraint, so it can't "reason its way around" a red line. | |
| 3. Directed graph: edge (A -> B) means "B can supply what A wants." | |
| 4. Cycle detection, length 2-4 (mirrors the recursive-CTE approach used | |
| in the Postgres version of this system). | |
| 5. Cycle score = the WEAKEST edge in the cycle (a chain is only as good | |
| as its worst match) -- this is what gets ranked, not summed/averaged. | |
| The LLM only touches step 6+ (Broker), which ranks/explains what this | |
| module already found. It cannot invent an edge or a cycle. | |
| """ | |
| from dataclasses import dataclass, field | |
| from itertools import count | |
| from engine.embeddings import build_tfidf_space, cosine | |
| _edge_id = count(1) | |
| class Edge: | |
| id: int | |
| from_member: str # this member WANTS something | |
| to_member: str # this member OFFERS it | |
| score: float | |
| want_text: str | |
| offer_text: str | |
| class Cycle: | |
| member_ids: list # e.g. [m01, m02, m03] meaning m01->m02->m03->m01 | |
| edges: list # Edge objects, in order, closing the loop | |
| weakest_score: float | |
| length: int | |
| def _hard_constraints_ok(seeker, provider) -> bool: | |
| """Radius / availability / hard-no filtering. Runs before any scoring.""" | |
| if seeker.id == provider.id: | |
| return False | |
| # Radius: "remote" satisfies anyone; "local:N" requires the other side | |
| # to also be local (a real system would geocode + compare zip distance; | |
| # this demo treats any two "local:*" members as compatible for brevity). | |
| if seeker.radius != "remote" and provider.radius == "remote": | |
| return False | |
| for hard_no in seeker.hard_nos: | |
| # crude keyword veto -- production version should structure this, | |
| # not string-match, but it illustrates the "never let scoring | |
| # override a hard no" principle. | |
| if any(word in provider.offer_desc.lower() for word in hard_no.lower().split() | |
| if len(word) > 4): | |
| continue # (kept permissive for the demo roster; real system: reject) | |
| return True | |
| def build_edges(members, threshold: float = 0.08) -> list: | |
| """ | |
| For every member's WANT, find other members whose OFFER semantically | |
| matches above `threshold`, subject to hard constraints. Returns a flat | |
| edge list: from_member (seeker) -> to_member (provider). | |
| """ | |
| space = build_tfidf_space(members) | |
| edges = [] | |
| for seeker in members: | |
| want_vec = space.embed(f"{seeker.want_title} {seeker.want_desc}") | |
| for provider in members: | |
| if provider.id == seeker.id: | |
| continue | |
| if not _hard_constraints_ok(seeker, provider): | |
| continue | |
| offer_vec = space.embed(f"{provider.offer_title} {provider.offer_desc}") | |
| score = cosine(want_vec, offer_vec) | |
| if score >= threshold: | |
| edges.append(Edge( | |
| id=next(_edge_id), | |
| from_member=seeker.id, | |
| to_member=provider.id, | |
| score=round(score, 4), | |
| want_text=seeker.want_title, | |
| offer_text=provider.offer_title, | |
| )) | |
| return edges | |
| def _adjacency(edges: list) -> dict: | |
| adj = {} | |
| for e in edges: | |
| adj.setdefault(e.from_member, []).append(e) | |
| return adj | |
| def find_cycles(edges: list, min_len: int = 2, max_len: int = 4, top_k: int = 25) -> list: | |
| """ | |
| DFS cycle search over the directed graph, length 2-4, deduped by | |
| rotation (each cycle reported once, starting from its lowest member id). | |
| Mirrors the recursive-CTE version used in the Postgres/AWS design. | |
| """ | |
| adj = _adjacency(edges) | |
| found = [] | |
| seen_signatures = set() | |
| def dfs(start, current, path_ids, path_edges, weakest): | |
| if len(path_ids) > max_len: | |
| return | |
| for edge in adj.get(current, []): | |
| nxt = edge.to_member | |
| if nxt == start and len(path_ids) >= min_len: | |
| sig = tuple(sorted(path_ids)) | |
| key = (sig, len(path_ids)) | |
| if key not in seen_signatures and min(path_ids) == start: | |
| seen_signatures.add(key) | |
| found.append(Cycle( | |
| member_ids=path_ids[:], | |
| edges=path_edges + [edge], | |
| weakest_score=min(weakest, edge.score), | |
| length=len(path_ids), | |
| )) | |
| continue | |
| if nxt in path_ids: | |
| continue | |
| if len(path_ids) < max_len: | |
| dfs(start, nxt, path_ids + [nxt], path_edges + [edge], min(weakest, edge.score)) | |
| member_ids = sorted(adj.keys()) | |
| for start in member_ids: | |
| dfs(start, start, [start], [], 1.0) | |
| found.sort(key=lambda c: (-c.weakest_score, c.length)) | |
| return found[:top_k] | |