diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index f1b883498b1..7023d1a87e5 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -125,15 +125,42 @@ jobs: DD_SERVICE: weave-python DD_ENV: ci WEAVE_SENTRY_ENV: ci + CI: 1 + WB_SERVER_HOST: http://wandbservice + WF_CLICKHOUSE_HOST: weave_clickhouse + WEAVE_SERVER_DISABLE_ECOSYSTEM: 1 # This runner specifically runs the tests that use the `client` fixture (those that support clickhouse client tests) # However, we skip tests marked with `skip_clickhouse_client`. These should be considered TODOs and an exception - run: CI=1 WB_SERVER_HOST=http://wandbservice WF_CLICKHOUSE_HOST=weave_clickhouse WEAVE_SERVER_DISABLE_ECOSYSTEM=1 source /root/venv/bin/activate && cd weave && pytest -m "weave_client and not skip_clickhouse_client" --weave-server=clickhouse --job-num=${{ matrix.job_num }} --timeout=90 ./flow ./integrations ./tests ./legacy/ops_arrow ./legacy/ecosystem ./trace_server ./trace --ddtrace --durations=5 + run: | + source /root/venv/bin/activate && \ + cd weave && \ + pytest -m "weave_client and not skip_clickhouse_client" \ + --weave-server=clickhouse \ + --job-num=${{ matrix.job_num }} \ + --timeout=90 \ + --ddtrace \ + --durations=5 \ + ./integrations ./legacy ./trace_server ./trace ./tests - name: Run Python Unit Tests env: DD_SERVICE: weave-python DD_ENV: ci WEAVE_SENTRY_ENV: ci - run: CI=1 WB_SERVER_HOST=http://wandbservice WF_CLICKHOUSE_HOST=weave_clickhouse WEAVE_SERVER_DISABLE_ECOSYSTEM=1 source /root/venv/bin/activate && pip install 'dspy-ai>=0.1.5' && pip install pydantic -U && cd weave && pytest --job-num=${{ matrix.job_num }} --timeout=90 ./flow ./integrations ./tests ./legacy/ops_arrow ./legacy/ecosystem ./trace_server ./trace --ddtrace --durations=5 + CI: 1 + WB_SERVER_HOST: http://wandbservice + WF_CLICKHOUSE_HOST: weave_clickhouse + WEAVE_SERVER_DISABLE_ECOSYSTEM: 1 + run: | + source /root/venv/bin/activate && \ + pip install 'dspy-ai>=0.1.5' && \ + pip install pydantic -U && \ + cd weave && \ + pytest \ + --job-num=${{ matrix.job_num }} \ + --timeout=90 \ + --ddtrace \ + --durations=5 \ + ./integrations ./legacy ./trace_server ./trace ./tests # nbmake: # name: Run notebooks with nbmake diff --git a/data/paul_graham/paul_graham_essay.txt b/data/paul_graham/paul_graham_essay.txt deleted file mode 100644 index 6ebd8587939..00000000000 --- a/data/paul_graham/paul_graham_essay.txt +++ /dev/null @@ -1,353 +0,0 @@ - - -What I Worked On - -February 2021 - -Before college the two main things I worked on, outside of school, were writing and programming. I didn't write essays. I wrote what beginning writers were supposed to write then, and probably still are: short stories. My stories were awful. They had hardly any plot, just characters with strong feelings, which I imagined made them deep. - -The first programs I tried writing were on the IBM 1401 that our school district used for what was then called "data processing." This was in 9th grade, so I was 13 or 14. The school district's 1401 happened to be in the basement of our junior high school, and my friend Rich Draves and I got permission to use it. It was like a mini Bond villain's lair down there, with all these alien-looking machines — CPU, disk drives, printer, card reader — sitting up on a raised floor under bright fluorescent lights. - -The language we used was an early version of Fortran. You had to type programs on punch cards, then stack them in the card reader and press a button to load the program into memory and run it. The result would ordinarily be to print something on the spectacularly loud printer. - -I was puzzled by the 1401. I couldn't figure out what to do with it. And in retrospect there's not much I could have done with it. The only form of input to programs was data stored on punched cards, and I didn't have any data stored on punched cards. The only other option was to do things that didn't rely on any input, like calculate approximations of pi, but I didn't know enough math to do anything interesting of that type. So I'm not surprised I can't remember any programs I wrote, because they can't have done much. My clearest memory is of the moment I learned it was possible for programs not to terminate, when one of mine didn't. On a machine without time-sharing, this was a social as well as a technical error, as the data center manager's expression made clear. - -With microcomputers, everything changed. Now you could have a computer sitting right in front of you, on a desk, that could respond to your keystrokes as it was running instead of just churning through a stack of punch cards and then stopping. [1] - -The first of my friends to get a microcomputer built it himself. It was sold as a kit by Heathkit. I remember vividly how impressed and envious I felt watching him sitting in front of it, typing programs right into the computer. - -Computers were expensive in those days and it took me years of nagging before I convinced my father to buy one, a TRS-80, in about 1980. The gold standard then was the Apple II, but a TRS-80 was good enough. This was when I really started programming. I wrote simple games, a program to predict how high my model rockets would fly, and a word processor that my father used to write at least one book. There was only room in memory for about 2 pages of text, so he'd write 2 pages at a time and then print them out, but it was a lot better than a typewriter. - -Though I liked programming, I didn't plan to study it in college. In college I was going to study philosophy, which sounded much more powerful. It seemed, to my naive high school self, to be the study of the ultimate truths, compared to which the things studied in other fields would be mere domain knowledge. What I discovered when I got to college was that the other fields took up so much of the space of ideas that there wasn't much left for these supposed ultimate truths. All that seemed left for philosophy were edge cases that people in other fields felt could safely be ignored. - -I couldn't have put this into words when I was 18. All I knew at the time was that I kept taking philosophy courses and they kept being boring. So I decided to switch to AI. - -AI was in the air in the mid 1980s, but there were two things especially that made me want to work on it: a novel by Heinlein called The Moon is a Harsh Mistress, which featured an intelligent computer called Mike, and a PBS documentary that showed Terry Winograd using SHRDLU. I haven't tried rereading The Moon is a Harsh Mistress, so I don't know how well it has aged, but when I read it I was drawn entirely into its world. It seemed only a matter of time before we'd have Mike, and when I saw Winograd using SHRDLU, it seemed like that time would be a few years at most. All you had to do was teach SHRDLU more words. - -There weren't any classes in AI at Cornell then, not even graduate classes, so I started trying to teach myself. Which meant learning Lisp, since in those days Lisp was regarded as the language of AI. The commonly used programming languages then were pretty primitive, and programmers' ideas correspondingly so. The default language at Cornell was a Pascal-like language called PL/I, and the situation was similar elsewhere. Learning Lisp expanded my concept of a program so fast that it was years before I started to have a sense of where the new limits were. This was more like it; this was what I had expected college to do. It wasn't happening in a class, like it was supposed to, but that was ok. For the next couple years I was on a roll. I knew what I was going to do. - -For my undergraduate thesis, I reverse-engineered SHRDLU. My God did I love working on that program. It was a pleasing bit of code, but what made it even more exciting was my belief — hard to imagine now, but not unique in 1985 — that it was already climbing the lower slopes of intelligence. - -I had gotten into a program at Cornell that didn't make you choose a major. You could take whatever classes you liked, and choose whatever you liked to put on your degree. I of course chose "Artificial Intelligence." When I got the actual physical diploma, I was dismayed to find that the quotes had been included, which made them read as scare-quotes. At the time this bothered me, but now it seems amusingly accurate, for reasons I was about to discover. - -I applied to 3 grad schools: MIT and Yale, which were renowned for AI at the time, and Harvard, which I'd visited because Rich Draves went there, and was also home to Bill Woods, who'd invented the type of parser I used in my SHRDLU clone. Only Harvard accepted me, so that was where I went. - -I don't remember the moment it happened, or if there even was a specific moment, but during the first year of grad school I realized that AI, as practiced at the time, was a hoax. By which I mean the sort of AI in which a program that's told "the dog is sitting on the chair" translates this into some formal representation and adds it to the list of things it knows. - -What these programs really showed was that there's a subset of natural language that's a formal language. But a very proper subset. It was clear that there was an unbridgeable gap between what they could do and actually understanding natural language. It was not, in fact, simply a matter of teaching SHRDLU more words. That whole way of doing AI, with explicit data structures representing concepts, was not going to work. Its brokenness did, as so often happens, generate a lot of opportunities to write papers about various band-aids that could be applied to it, but it was never going to get us Mike. - -So I looked around to see what I could salvage from the wreckage of my plans, and there was Lisp. I knew from experience that Lisp was interesting for its own sake and not just for its association with AI, even though that was the main reason people cared about it at the time. So I decided to focus on Lisp. In fact, I decided to write a book about Lisp hacking. It's scary to think how little I knew about Lisp hacking when I started writing that book. But there's nothing like writing a book about something to help you learn it. The book, On Lisp, wasn't published till 1993, but I wrote much of it in grad school. - -Computer Science is an uneasy alliance between two halves, theory and systems. The theory people prove things, and the systems people build things. I wanted to build things. I had plenty of respect for theory — indeed, a sneaking suspicion that it was the more admirable of the two halves — but building things seemed so much more exciting. - -The problem with systems work, though, was that it didn't last. Any program you wrote today, no matter how good, would be obsolete in a couple decades at best. People might mention your software in footnotes, but no one would actually use it. And indeed, it would seem very feeble work. Only people with a sense of the history of the field would even realize that, in its time, it had been good. - -There were some surplus Xerox Dandelions floating around the computer lab at one point. Anyone who wanted one to play around with could have one. I was briefly tempted, but they were so slow by present standards; what was the point? No one else wanted one either, so off they went. That was what happened to systems work. - -I wanted not just to build things, but to build things that would last. - -In this dissatisfied state I went in 1988 to visit Rich Draves at CMU, where he was in grad school. One day I went to visit the Carnegie Institute, where I'd spent a lot of time as a kid. While looking at a painting there I realized something that might seem obvious, but was a big surprise to me. There, right on the wall, was something you could make that would last. Paintings didn't become obsolete. Some of the best ones were hundreds of years old. - -And moreover this was something you could make a living doing. Not as easily as you could by writing software, of course, but I thought if you were really industrious and lived really cheaply, it had to be possible to make enough to survive. And as an artist you could be truly independent. You wouldn't have a boss, or even need to get research funding. - -I had always liked looking at paintings. Could I make them? I had no idea. I'd never imagined it was even possible. I knew intellectually that people made art — that it didn't just appear spontaneously — but it was as if the people who made it were a different species. They either lived long ago or were mysterious geniuses doing strange things in profiles in Life magazine. The idea of actually being able to make art, to put that verb before that noun, seemed almost miraculous. - -That fall I started taking art classes at Harvard. Grad students could take classes in any department, and my advisor, Tom Cheatham, was very easy going. If he even knew about the strange classes I was taking, he never said anything. - -So now I was in a PhD program in computer science, yet planning to be an artist, yet also genuinely in love with Lisp hacking and working away at On Lisp. In other words, like many a grad student, I was working energetically on multiple projects that were not my thesis. - -I didn't see a way out of this situation. I didn't want to drop out of grad school, but how else was I going to get out? I remember when my friend Robert Morris got kicked out of Cornell for writing the internet worm of 1988, I was envious that he'd found such a spectacular way to get out of grad school. - -Then one day in April 1990 a crack appeared in the wall. I ran into professor Cheatham and he asked if I was far enough along to graduate that June. I didn't have a word of my dissertation written, but in what must have been the quickest bit of thinking in my life, I decided to take a shot at writing one in the 5 weeks or so that remained before the deadline, reusing parts of On Lisp where I could, and I was able to respond, with no perceptible delay "Yes, I think so. I'll give you something to read in a few days." - -I picked applications of continuations as the topic. In retrospect I should have written about macros and embedded languages. There's a whole world there that's barely been explored. But all I wanted was to get out of grad school, and my rapidly written dissertation sufficed, just barely. - -Meanwhile I was applying to art schools. I applied to two: RISD in the US, and the Accademia di Belli Arti in Florence, which, because it was the oldest art school, I imagined would be good. RISD accepted me, and I never heard back from the Accademia, so off to Providence I went. - -I'd applied for the BFA program at RISD, which meant in effect that I had to go to college again. This was not as strange as it sounds, because I was only 25, and art schools are full of people of different ages. RISD counted me as a transfer sophomore and said I had to do the foundation that summer. The foundation means the classes that everyone has to take in fundamental subjects like drawing, color, and design. - -Toward the end of the summer I got a big surprise: a letter from the Accademia, which had been delayed because they'd sent it to Cambridge England instead of Cambridge Massachusetts, inviting me to take the entrance exam in Florence that fall. This was now only weeks away. My nice landlady let me leave my stuff in her attic. I had some money saved from consulting work I'd done in grad school; there was probably enough to last a year if I lived cheaply. Now all I had to do was learn Italian. - -Only stranieri (foreigners) had to take this entrance exam. In retrospect it may well have been a way of excluding them, because there were so many stranieri attracted by the idea of studying art in Florence that the Italian students would otherwise have been outnumbered. I was in decent shape at painting and drawing from the RISD foundation that summer, but I still don't know how I managed to pass the written exam. I remember that I answered the essay question by writing about Cezanne, and that I cranked up the intellectual level as high as I could to make the most of my limited vocabulary. [2] - -I'm only up to age 25 and already there are such conspicuous patterns. Here I was, yet again about to attend some august institution in the hopes of learning about some prestigious subject, and yet again about to be disappointed. The students and faculty in the painting department at the Accademia were the nicest people you could imagine, but they had long since arrived at an arrangement whereby the students wouldn't require the faculty to teach anything, and in return the faculty wouldn't require the students to learn anything. And at the same time all involved would adhere outwardly to the conventions of a 19th century atelier. We actually had one of those little stoves, fed with kindling, that you see in 19th century studio paintings, and a nude model sitting as close to it as possible without getting burned. Except hardly anyone else painted her besides me. The rest of the students spent their time chatting or occasionally trying to imitate things they'd seen in American art magazines. - -Our model turned out to live just down the street from me. She made a living from a combination of modelling and making fakes for a local antique dealer. She'd copy an obscure old painting out of a book, and then he'd take the copy and maltreat it to make it look old. [3] - -While I was a student at the Accademia I started painting still lives in my bedroom at night. These paintings were tiny, because the room was, and because I painted them on leftover scraps of canvas, which was all I could afford at the time. Painting still lives is different from painting people, because the subject, as its name suggests, can't move. People can't sit for more than about 15 minutes at a time, and when they do they don't sit very still. So the traditional m.o. for painting people is to know how to paint a generic person, which you then modify to match the specific person you're painting. Whereas a still life you can, if you want, copy pixel by pixel from what you're seeing. You don't want to stop there, of course, or you get merely photographic accuracy, and what makes a still life interesting is that it's been through a head. You want to emphasize the visual cues that tell you, for example, that the reason the color changes suddenly at a certain point is that it's the edge of an object. By subtly emphasizing such things you can make paintings that are more realistic than photographs not just in some metaphorical sense, but in the strict information-theoretic sense. [4] - -I liked painting still lives because I was curious about what I was seeing. In everyday life, we aren't consciously aware of much we're seeing. Most visual perception is handled by low-level processes that merely tell your brain "that's a water droplet" without telling you details like where the lightest and darkest points are, or "that's a bush" without telling you the shape and position of every leaf. This is a feature of brains, not a bug. In everyday life it would be distracting to notice every leaf on every bush. But when you have to paint something, you have to look more closely, and when you do there's a lot to see. You can still be noticing new things after days of trying to paint something people usually take for granted, just as you can after days of trying to write an essay about something people usually take for granted. - -This is not the only way to paint. I'm not 100% sure it's even a good way to paint. But it seemed a good enough bet to be worth trying. - -Our teacher, professor Ulivi, was a nice guy. He could see I worked hard, and gave me a good grade, which he wrote down in a sort of passport each student had. But the Accademia wasn't teaching me anything except Italian, and my money was running out, so at the end of the first year I went back to the US. - -I wanted to go back to RISD, but I was now broke and RISD was very expensive, so I decided to get a job for a year and then return to RISD the next fall. I got one at a company called Interleaf, which made software for creating documents. You mean like Microsoft Word? Exactly. That was how I learned that low end software tends to eat high end software. But Interleaf still had a few years to live yet. [5] - -Interleaf had done something pretty bold. Inspired by Emacs, they'd added a scripting language, and even made the scripting language a dialect of Lisp. Now they wanted a Lisp hacker to write things in it. This was the closest thing I've had to a normal job, and I hereby apologize to my boss and coworkers, because I was a bad employee. Their Lisp was the thinnest icing on a giant C cake, and since I didn't know C and didn't want to learn it, I never understood most of the software. Plus I was terribly irresponsible. This was back when a programming job meant showing up every day during certain working hours. That seemed unnatural to me, and on this point the rest of the world is coming around to my way of thinking, but at the time it caused a lot of friction. Toward the end of the year I spent much of my time surreptitiously working on On Lisp, which I had by this time gotten a contract to publish. - -The good part was that I got paid huge amounts of money, especially by art student standards. In Florence, after paying my part of the rent, my budget for everything else had been $7 a day. Now I was getting paid more than 4 times that every hour, even when I was just sitting in a meeting. By living cheaply I not only managed to save enough to go back to RISD, but also paid off my college loans. - -I learned some useful things at Interleaf, though they were mostly about what not to do. I learned that it's better for technology companies to be run by product people than sales people (though sales is a real skill and people who are good at it are really good at it), that it leads to bugs when code is edited by too many people, that cheap office space is no bargain if it's depressing, that planned meetings are inferior to corridor conversations, that big, bureaucratic customers are a dangerous source of money, and that there's not much overlap between conventional office hours and the optimal time for hacking, or conventional offices and the optimal place for it. - -But the most important thing I learned, and which I used in both Viaweb and Y Combinator, is that the low end eats the high end: that it's good to be the "entry level" option, even though that will be less prestigious, because if you're not, someone else will be, and will squash you against the ceiling. Which in turn means that prestige is a danger sign. - -When I left to go back to RISD the next fall, I arranged to do freelance work for the group that did projects for customers, and this was how I survived for the next several years. When I came back to visit for a project later on, someone told me about a new thing called HTML, which was, as he described it, a derivative of SGML. Markup language enthusiasts were an occupational hazard at Interleaf and I ignored him, but this HTML thing later became a big part of my life. - -In the fall of 1992 I moved back to Providence to continue at RISD. The foundation had merely been intro stuff, and the Accademia had been a (very civilized) joke. Now I was going to see what real art school was like. But alas it was more like the Accademia than not. Better organized, certainly, and a lot more expensive, but it was now becoming clear that art school did not bear the same relationship to art that medical school bore to medicine. At least not the painting department. The textile department, which my next door neighbor belonged to, seemed to be pretty rigorous. No doubt illustration and architecture were too. But painting was post-rigorous. Painting students were supposed to express themselves, which to the more worldly ones meant to try to cook up some sort of distinctive signature style. - -A signature style is the visual equivalent of what in show business is known as a "schtick": something that immediately identifies the work as yours and no one else's. For example, when you see a painting that looks like a certain kind of cartoon, you know it's by Roy Lichtenstein. So if you see a big painting of this type hanging in the apartment of a hedge fund manager, you know he paid millions of dollars for it. That's not always why artists have a signature style, but it's usually why buyers pay a lot for such work. [6] - -There were plenty of earnest students too: kids who "could draw" in high school, and now had come to what was supposed to be the best art school in the country, to learn to draw even better. They tended to be confused and demoralized by what they found at RISD, but they kept going, because painting was what they did. I was not one of the kids who could draw in high school, but at RISD I was definitely closer to their tribe than the tribe of signature style seekers. - -I learned a lot in the color class I took at RISD, but otherwise I was basically teaching myself to paint, and I could do that for free. So in 1993 I dropped out. I hung around Providence for a bit, and then my college friend Nancy Parmet did me a big favor. A rent-controlled apartment in a building her mother owned in New York was becoming vacant. Did I want it? It wasn't much more than my current place, and New York was supposed to be where the artists were. So yes, I wanted it! [7] - -Asterix comics begin by zooming in on a tiny corner of Roman Gaul that turns out not to be controlled by the Romans. You can do something similar on a map of New York City: if you zoom in on the Upper East Side, there's a tiny corner that's not rich, or at least wasn't in 1993. It's called Yorkville, and that was my new home. Now I was a New York artist — in the strictly technical sense of making paintings and living in New York. - -I was nervous about money, because I could sense that Interleaf was on the way down. Freelance Lisp hacking work was very rare, and I didn't want to have to program in another language, which in those days would have meant C++ if I was lucky. So with my unerring nose for financial opportunity, I decided to write another book on Lisp. This would be a popular book, the sort of book that could be used as a textbook. I imagined myself living frugally off the royalties and spending all my time painting. (The painting on the cover of this book, ANSI Common Lisp, is one that I painted around this time.) - -The best thing about New York for me was the presence of Idelle and Julian Weber. Idelle Weber was a painter, one of the early photorealists, and I'd taken her painting class at Harvard. I've never known a teacher more beloved by her students. Large numbers of former students kept in touch with her, including me. After I moved to New York I became her de facto studio assistant. - -She liked to paint on big, square canvases, 4 to 5 feet on a side. One day in late 1994 as I was stretching one of these monsters there was something on the radio about a famous fund manager. He wasn't that much older than me, and was super rich. The thought suddenly occurred to me: why don't I become rich? Then I'll be able to work on whatever I want. - -Meanwhile I'd been hearing more and more about this new thing called the World Wide Web. Robert Morris showed it to me when I visited him in Cambridge, where he was now in grad school at Harvard. It seemed to me that the web would be a big deal. I'd seen what graphical user interfaces had done for the popularity of microcomputers. It seemed like the web would do the same for the internet. - -If I wanted to get rich, here was the next train leaving the station. I was right about that part. What I got wrong was the idea. I decided we should start a company to put art galleries online. I can't honestly say, after reading so many Y Combinator applications, that this was the worst startup idea ever, but it was up there. Art galleries didn't want to be online, and still don't, not the fancy ones. That's not how they sell. I wrote some software to generate web sites for galleries, and Robert wrote some to resize images and set up an http server to serve the pages. Then we tried to sign up galleries. To call this a difficult sale would be an understatement. It was difficult to give away. A few galleries let us make sites for them for free, but none paid us. - -Then some online stores started to appear, and I realized that except for the order buttons they were identical to the sites we'd been generating for galleries. This impressive-sounding thing called an "internet storefront" was something we already knew how to build. - -So in the summer of 1995, after I submitted the camera-ready copy of ANSI Common Lisp to the publishers, we started trying to write software to build online stores. At first this was going to be normal desktop software, which in those days meant Windows software. That was an alarming prospect, because neither of us knew how to write Windows software or wanted to learn. We lived in the Unix world. But we decided we'd at least try writing a prototype store builder on Unix. Robert wrote a shopping cart, and I wrote a new site generator for stores — in Lisp, of course. - -We were working out of Robert's apartment in Cambridge. His roommate was away for big chunks of time, during which I got to sleep in his room. For some reason there was no bed frame or sheets, just a mattress on the floor. One morning as I was lying on this mattress I had an idea that made me sit up like a capital L. What if we ran the software on the server, and let users control it by clicking on links? Then we'd never have to write anything to run on users' computers. We could generate the sites on the same server we'd serve them from. Users wouldn't need anything more than a browser. - -This kind of software, known as a web app, is common now, but at the time it wasn't clear that it was even possible. To find out, we decided to try making a version of our store builder that you could control through the browser. A couple days later, on August 12, we had one that worked. The UI was horrible, but it proved you could build a whole store through the browser, without any client software or typing anything into the command line on the server. - -Now we felt like we were really onto something. I had visions of a whole new generation of software working this way. You wouldn't need versions, or ports, or any of that crap. At Interleaf there had been a whole group called Release Engineering that seemed to be at least as big as the group that actually wrote the software. Now you could just update the software right on the server. - -We started a new company we called Viaweb, after the fact that our software worked via the web, and we got $10,000 in seed funding from Idelle's husband Julian. In return for that and doing the initial legal work and giving us business advice, we gave him 10% of the company. Ten years later this deal became the model for Y Combinator's. We knew founders needed something like this, because we'd needed it ourselves. - -At this stage I had a negative net worth, because the thousand dollars or so I had in the bank was more than counterbalanced by what I owed the government in taxes. (Had I diligently set aside the proper proportion of the money I'd made consulting for Interleaf? No, I had not.) So although Robert had his graduate student stipend, I needed that seed funding to live on. - -We originally hoped to launch in September, but we got more ambitious about the software as we worked on it. Eventually we managed to build a WYSIWYG site builder, in the sense that as you were creating pages, they looked exactly like the static ones that would be generated later, except that instead of leading to static pages, the links all referred to closures stored in a hash table on the server. - -It helped to have studied art, because the main goal of an online store builder is to make users look legit, and the key to looking legit is high production values. If you get page layouts and fonts and colors right, you can make a guy running a store out of his bedroom look more legit than a big company. - -(If you're curious why my site looks so old-fashioned, it's because it's still made with this software. It may look clunky today, but in 1996 it was the last word in slick.) - -In September, Robert rebelled. "We've been working on this for a month," he said, "and it's still not done." This is funny in retrospect, because he would still be working on it almost 3 years later. But I decided it might be prudent to recruit more programmers, and I asked Robert who else in grad school with him was really good. He recommended Trevor Blackwell, which surprised me at first, because at that point I knew Trevor mainly for his plan to reduce everything in his life to a stack of notecards, which he carried around with him. But Rtm was right, as usual. Trevor turned out to be a frighteningly effective hacker. - -It was a lot of fun working with Robert and Trevor. They're the two most independent-minded people I know, and in completely different ways. If you could see inside Rtm's brain it would look like a colonial New England church, and if you could see inside Trevor's it would look like the worst excesses of Austrian Rococo. - -We opened for business, with 6 stores, in January 1996. It was just as well we waited a few months, because although we worried we were late, we were actually almost fatally early. There was a lot of talk in the press then about ecommerce, but not many people actually wanted online stores. [8] - -There were three main parts to the software: the editor, which people used to build sites and which I wrote, the shopping cart, which Robert wrote, and the manager, which kept track of orders and statistics, and which Trevor wrote. In its time, the editor was one of the best general-purpose site builders. I kept the code tight and didn't have to integrate with any other software except Robert's and Trevor's, so it was quite fun to work on. If all I'd had to do was work on this software, the next 3 years would have been the easiest of my life. Unfortunately I had to do a lot more, all of it stuff I was worse at than programming, and the next 3 years were instead the most stressful. - -There were a lot of startups making ecommerce software in the second half of the 90s. We were determined to be the Microsoft Word, not the Interleaf. Which meant being easy to use and inexpensive. It was lucky for us that we were poor, because that caused us to make Viaweb even more inexpensive than we realized. We charged $100 a month for a small store and $300 a month for a big one. This low price was a big attraction, and a constant thorn in the sides of competitors, but it wasn't because of some clever insight that we set the price low. We had no idea what businesses paid for things. $300 a month seemed like a lot of money to us. - -We did a lot of things right by accident like that. For example, we did what's now called "doing things that don't scale," although at the time we would have described it as "being so lame that we're driven to the most desperate measures to get users." The most common of which was building stores for them. This seemed particularly humiliating, since the whole raison d'etre of our software was that people could use it to make their own stores. But anything to get users. - -We learned a lot more about retail than we wanted to know. For example, that if you could only have a small image of a man's shirt (and all images were small then by present standards), it was better to have a closeup of the collar than a picture of the whole shirt. The reason I remember learning this was that it meant I had to rescan about 30 images of men's shirts. My first set of scans were so beautiful too. - -Though this felt wrong, it was exactly the right thing to be doing. Building stores for users taught us about retail, and about how it felt to use our software. I was initially both mystified and repelled by "business" and thought we needed a "business person" to be in charge of it, but once we started to get users, I was converted, in much the same way I was converted to fatherhood once I had kids. Whatever users wanted, I was all theirs. Maybe one day we'd have so many users that I couldn't scan their images for them, but in the meantime there was nothing more important to do. - -Another thing I didn't get at the time is that growth rate is the ultimate test of a startup. Our growth rate was fine. We had about 70 stores at the end of 1996 and about 500 at the end of 1997. I mistakenly thought the thing that mattered was the absolute number of users. And that is the thing that matters in the sense that that's how much money you're making, and if you're not making enough, you might go out of business. But in the long term the growth rate takes care of the absolute number. If we'd been a startup I was advising at Y Combinator, I would have said: Stop being so stressed out, because you're doing fine. You're growing 7x a year. Just don't hire too many more people and you'll soon be profitable, and then you'll control your own destiny. - -Alas I hired lots more people, partly because our investors wanted me to, and partly because that's what startups did during the Internet Bubble. A company with just a handful of employees would have seemed amateurish. So we didn't reach breakeven until about when Yahoo bought us in the summer of 1998. Which in turn meant we were at the mercy of investors for the entire life of the company. And since both we and our investors were noobs at startups, the result was a mess even by startup standards. - -It was a huge relief when Yahoo bought us. In principle our Viaweb stock was valuable. It was a share in a business that was profitable and growing rapidly. But it didn't feel very valuable to me; I had no idea how to value a business, but I was all too keenly aware of the near-death experiences we seemed to have every few months. Nor had I changed my grad student lifestyle significantly since we started. So when Yahoo bought us it felt like going from rags to riches. Since we were going to California, I bought a car, a yellow 1998 VW GTI. I remember thinking that its leather seats alone were by far the most luxurious thing I owned. - -The next year, from the summer of 1998 to the summer of 1999, must have been the least productive of my life. I didn't realize it at the time, but I was worn out from the effort and stress of running Viaweb. For a while after I got to California I tried to continue my usual m.o. of programming till 3 in the morning, but fatigue combined with Yahoo's prematurely aged culture and grim cube farm in Santa Clara gradually dragged me down. After a few months it felt disconcertingly like working at Interleaf. - -Yahoo had given us a lot of options when they bought us. At the time I thought Yahoo was so overvalued that they'd never be worth anything, but to my astonishment the stock went up 5x in the next year. I hung on till the first chunk of options vested, then in the summer of 1999 I left. It had been so long since I'd painted anything that I'd half forgotten why I was doing this. My brain had been entirely full of software and men's shirts for 4 years. But I had done this to get rich so I could paint, I reminded myself, and now I was rich, so I should go paint. - -When I said I was leaving, my boss at Yahoo had a long conversation with me about my plans. I told him all about the kinds of pictures I wanted to paint. At the time I was touched that he took such an interest in me. Now I realize it was because he thought I was lying. My options at that point were worth about $2 million a month. If I was leaving that kind of money on the table, it could only be to go and start some new startup, and if I did, I might take people with me. This was the height of the Internet Bubble, and Yahoo was ground zero of it. My boss was at that moment a billionaire. Leaving then to start a new startup must have seemed to him an insanely, and yet also plausibly, ambitious plan. - -But I really was quitting to paint, and I started immediately. There was no time to lose. I'd already burned 4 years getting rich. Now when I talk to founders who are leaving after selling their companies, my advice is always the same: take a vacation. That's what I should have done, just gone off somewhere and done nothing for a month or two, but the idea never occurred to me. - -So I tried to paint, but I just didn't seem to have any energy or ambition. Part of the problem was that I didn't know many people in California. I'd compounded this problem by buying a house up in the Santa Cruz Mountains, with a beautiful view but miles from anywhere. I stuck it out for a few more months, then in desperation I went back to New York, where unless you understand about rent control you'll be surprised to hear I still had my apartment, sealed up like a tomb of my old life. Idelle was in New York at least, and there were other people trying to paint there, even though I didn't know any of them. - -When I got back to New York I resumed my old life, except now I was rich. It was as weird as it sounds. I resumed all my old patterns, except now there were doors where there hadn't been. Now when I was tired of walking, all I had to do was raise my hand, and (unless it was raining) a taxi would stop to pick me up. Now when I walked past charming little restaurants I could go in and order lunch. It was exciting for a while. Painting started to go better. I experimented with a new kind of still life where I'd paint one painting in the old way, then photograph it and print it, blown up, on canvas, and then use that as the underpainting for a second still life, painted from the same objects (which hopefully hadn't rotted yet). - -Meanwhile I looked for an apartment to buy. Now I could actually choose what neighborhood to live in. Where, I asked myself and various real estate agents, is the Cambridge of New York? Aided by occasional visits to actual Cambridge, I gradually realized there wasn't one. Huh. - -Around this time, in the spring of 2000, I had an idea. It was clear from our experience with Viaweb that web apps were the future. Why not build a web app for making web apps? Why not let people edit code on our server through the browser, and then host the resulting applications for them? [9] You could run all sorts of services on the servers that these applications could use just by making an API call: making and receiving phone calls, manipulating images, taking credit card payments, etc. - -I got so excited about this idea that I couldn't think about anything else. It seemed obvious that this was the future. I didn't particularly want to start another company, but it was clear that this idea would have to be embodied as one, so I decided to move to Cambridge and start it. I hoped to lure Robert into working on it with me, but there I ran into a hitch. Robert was now a postdoc at MIT, and though he'd made a lot of money the last time I'd lured him into working on one of my schemes, it had also been a huge time sink. So while he agreed that it sounded like a plausible idea, he firmly refused to work on it. - -Hmph. Well, I'd do it myself then. I recruited Dan Giffin, who had worked for Viaweb, and two undergrads who wanted summer jobs, and we got to work trying to build what it's now clear is about twenty companies and several open source projects worth of software. The language for defining applications would of course be a dialect of Lisp. But I wasn't so naive as to assume I could spring an overt Lisp on a general audience; we'd hide the parentheses, like Dylan did. - -By then there was a name for the kind of company Viaweb was, an "application service provider," or ASP. This name didn't last long before it was replaced by "software as a service," but it was current for long enough that I named this new company after it: it was going to be called Aspra. - -I started working on the application builder, Dan worked on network infrastructure, and the two undergrads worked on the first two services (images and phone calls). But about halfway through the summer I realized I really didn't want to run a company — especially not a big one, which it was looking like this would have to be. I'd only started Viaweb because I needed the money. Now that I didn't need money anymore, why was I doing this? If this vision had to be realized as a company, then screw the vision. I'd build a subset that could be done as an open source project. - -Much to my surprise, the time I spent working on this stuff was not wasted after all. After we started Y Combinator, I would often encounter startups working on parts of this new architecture, and it was very useful to have spent so much time thinking about it and even trying to write some of it. - -The subset I would build as an open source project was the new Lisp, whose parentheses I now wouldn't even have to hide. A lot of Lisp hackers dream of building a new Lisp, partly because one of the distinctive features of the language is that it has dialects, and partly, I think, because we have in our minds a Platonic form of Lisp that all existing dialects fall short of. I certainly did. So at the end of the summer Dan and I switched to working on this new dialect of Lisp, which I called Arc, in a house I bought in Cambridge. - -The following spring, lightning struck. I was invited to give a talk at a Lisp conference, so I gave one about how we'd used Lisp at Viaweb. Afterward I put a postscript file of this talk online, on paulgraham.com, which I'd created years before using Viaweb but had never used for anything. In one day it got 30,000 page views. What on earth had happened? The referring urls showed that someone had posted it on Slashdot. [10] - -Wow, I thought, there's an audience. If I write something and put it on the web, anyone can read it. That may seem obvious now, but it was surprising then. In the print era there was a narrow channel to readers, guarded by fierce monsters known as editors. The only way to get an audience for anything you wrote was to get it published as a book, or in a newspaper or magazine. Now anyone could publish anything. - -This had been possible in principle since 1993, but not many people had realized it yet. I had been intimately involved with building the infrastructure of the web for most of that time, and a writer as well, and it had taken me 8 years to realize it. Even then it took me several years to understand the implications. It meant there would be a whole new generation of essays. [11] - -In the print era, the channel for publishing essays had been vanishingly small. Except for a few officially anointed thinkers who went to the right parties in New York, the only people allowed to publish essays were specialists writing about their specialties. There were so many essays that had never been written, because there had been no way to publish them. Now they could be, and I was going to write them. [12] - -I've worked on several different things, but to the extent there was a turning point where I figured out what to work on, it was when I started publishing essays online. From then on I knew that whatever else I did, I'd always write essays too. - -I knew that online essays would be a marginal medium at first. Socially they'd seem more like rants posted by nutjobs on their GeoCities sites than the genteel and beautifully typeset compositions published in The New Yorker. But by this point I knew enough to find that encouraging instead of discouraging. - -One of the most conspicuous patterns I've noticed in my life is how well it has worked, for me at least, to work on things that weren't prestigious. Still life has always been the least prestigious form of painting. Viaweb and Y Combinator both seemed lame when we started them. I still get the glassy eye from strangers when they ask what I'm writing, and I explain that it's an essay I'm going to publish on my web site. Even Lisp, though prestigious intellectually in something like the way Latin is, also seems about as hip. - -It's not that unprestigious types of work are good per se. But when you find yourself drawn to some kind of work despite its current lack of prestige, it's a sign both that there's something real to be discovered there, and that you have the right kind of motives. Impure motives are a big danger for the ambitious. If anything is going to lead you astray, it will be the desire to impress people. So while working on things that aren't prestigious doesn't guarantee you're on the right track, it at least guarantees you're not on the most common type of wrong one. - -Over the next several years I wrote lots of essays about all kinds of different topics. O'Reilly reprinted a collection of them as a book, called Hackers & Painters after one of the essays in it. I also worked on spam filters, and did some more painting. I used to have dinners for a group of friends every thursday night, which taught me how to cook for groups. And I bought another building in Cambridge, a former candy factory (and later, twas said, porn studio), to use as an office. - -One night in October 2003 there was a big party at my house. It was a clever idea of my friend Maria Daniels, who was one of the thursday diners. Three separate hosts would all invite their friends to one party. So for every guest, two thirds of the other guests would be people they didn't know but would probably like. One of the guests was someone I didn't know but would turn out to like a lot: a woman called Jessica Livingston. A couple days later I asked her out. - -Jessica was in charge of marketing at a Boston investment bank. This bank thought it understood startups, but over the next year, as she met friends of mine from the startup world, she was surprised how different reality was. And how colorful their stories were. So she decided to compile a book of interviews with startup founders. - -When the bank had financial problems and she had to fire half her staff, she started looking for a new job. In early 2005 she interviewed for a marketing job at a Boston VC firm. It took them weeks to make up their minds, and during this time I started telling her about all the things that needed to be fixed about venture capital. They should make a larger number of smaller investments instead of a handful of giant ones, they should be funding younger, more technical founders instead of MBAs, they should let the founders remain as CEO, and so on. - -One of my tricks for writing essays had always been to give talks. The prospect of having to stand up in front of a group of people and tell them something that won't waste their time is a great spur to the imagination. When the Harvard Computer Society, the undergrad computer club, asked me to give a talk, I decided I would tell them how to start a startup. Maybe they'd be able to avoid the worst of the mistakes we'd made. - -So I gave this talk, in the course of which I told them that the best sources of seed funding were successful startup founders, because then they'd be sources of advice too. Whereupon it seemed they were all looking expectantly at me. Horrified at the prospect of having my inbox flooded by business plans (if I'd only known), I blurted out "But not me!" and went on with the talk. But afterward it occurred to me that I should really stop procrastinating about angel investing. I'd been meaning to since Yahoo bought us, and now it was 7 years later and I still hadn't done one angel investment. - -Meanwhile I had been scheming with Robert and Trevor about projects we could work on together. I missed working with them, and it seemed like there had to be something we could collaborate on. - -As Jessica and I were walking home from dinner on March 11, at the corner of Garden and Walker streets, these three threads converged. Screw the VCs who were taking so long to make up their minds. We'd start our own investment firm and actually implement the ideas we'd been talking about. I'd fund it, and Jessica could quit her job and work for it, and we'd get Robert and Trevor as partners too. [13] - -Once again, ignorance worked in our favor. We had no idea how to be angel investors, and in Boston in 2005 there were no Ron Conways to learn from. So we just made what seemed like the obvious choices, and some of the things we did turned out to be novel. - -There are multiple components to Y Combinator, and we didn't figure them all out at once. The part we got first was to be an angel firm. In those days, those two words didn't go together. There were VC firms, which were organized companies with people whose job it was to make investments, but they only did big, million dollar investments. And there were angels, who did smaller investments, but these were individuals who were usually focused on other things and made investments on the side. And neither of them helped founders enough in the beginning. We knew how helpless founders were in some respects, because we remembered how helpless we'd been. For example, one thing Julian had done for us that seemed to us like magic was to get us set up as a company. We were fine writing fairly difficult software, but actually getting incorporated, with bylaws and stock and all that stuff, how on earth did you do that? Our plan was not only to make seed investments, but to do for startups everything Julian had done for us. - -YC was not organized as a fund. It was cheap enough to run that we funded it with our own money. That went right by 99% of readers, but professional investors are thinking "Wow, that means they got all the returns." But once again, this was not due to any particular insight on our part. We didn't know how VC firms were organized. It never occurred to us to try to raise a fund, and if it had, we wouldn't have known where to start. [14] - -The most distinctive thing about YC is the batch model: to fund a bunch of startups all at once, twice a year, and then to spend three months focusing intensively on trying to help them. That part we discovered by accident, not merely implicitly but explicitly due to our ignorance about investing. We needed to get experience as investors. What better way, we thought, than to fund a whole bunch of startups at once? We knew undergrads got temporary jobs at tech companies during the summer. Why not organize a summer program where they'd start startups instead? We wouldn't feel guilty for being in a sense fake investors, because they would in a similar sense be fake founders. So while we probably wouldn't make much money out of it, we'd at least get to practice being investors on them, and they for their part would probably have a more interesting summer than they would working at Microsoft. - -We'd use the building I owned in Cambridge as our headquarters. We'd all have dinner there once a week — on tuesdays, since I was already cooking for the thursday diners on thursdays — and after dinner we'd bring in experts on startups to give talks. - -We knew undergrads were deciding then about summer jobs, so in a matter of days we cooked up something we called the Summer Founders Program, and I posted an announcement on my site, inviting undergrads to apply. I had never imagined that writing essays would be a way to get "deal flow," as investors call it, but it turned out to be the perfect source. [15] We got 225 applications for the Summer Founders Program, and we were surprised to find that a lot of them were from people who'd already graduated, or were about to that spring. Already this SFP thing was starting to feel more serious than we'd intended. - -We invited about 20 of the 225 groups to interview in person, and from those we picked 8 to fund. They were an impressive group. That first batch included reddit, Justin Kan and Emmett Shear, who went on to found Twitch, Aaron Swartz, who had already helped write the RSS spec and would a few years later become a martyr for open access, and Sam Altman, who would later become the second president of YC. I don't think it was entirely luck that the first batch was so good. You had to be pretty bold to sign up for a weird thing like the Summer Founders Program instead of a summer job at a legit place like Microsoft or Goldman Sachs. - -The deal for startups was based on a combination of the deal we did with Julian ($10k for 10%) and what Robert said MIT grad students got for the summer ($6k). We invested $6k per founder, which in the typical two-founder case was $12k, in return for 6%. That had to be fair, because it was twice as good as the deal we ourselves had taken. Plus that first summer, which was really hot, Jessica brought the founders free air conditioners. [16] - -Fairly quickly I realized that we had stumbled upon the way to scale startup funding. Funding startups in batches was more convenient for us, because it meant we could do things for a lot of startups at once, but being part of a batch was better for the startups too. It solved one of the biggest problems faced by founders: the isolation. Now you not only had colleagues, but colleagues who understood the problems you were facing and could tell you how they were solving them. - -As YC grew, we started to notice other advantages of scale. The alumni became a tight community, dedicated to helping one another, and especially the current batch, whose shoes they remembered being in. We also noticed that the startups were becoming one another's customers. We used to refer jokingly to the "YC GDP," but as YC grows this becomes less and less of a joke. Now lots of startups get their initial set of customers almost entirely from among their batchmates. - -I had not originally intended YC to be a full-time job. I was going to do three things: hack, write essays, and work on YC. As YC grew, and I grew more excited about it, it started to take up a lot more than a third of my attention. But for the first few years I was still able to work on other things. - -In the summer of 2006, Robert and I started working on a new version of Arc. This one was reasonably fast, because it was compiled into Scheme. To test this new Arc, I wrote Hacker News in it. It was originally meant to be a news aggregator for startup founders and was called Startup News, but after a few months I got tired of reading about nothing but startups. Plus it wasn't startup founders we wanted to reach. It was future startup founders. So I changed the name to Hacker News and the topic to whatever engaged one's intellectual curiosity. - -HN was no doubt good for YC, but it was also by far the biggest source of stress for me. If all I'd had to do was select and help founders, life would have been so easy. And that implies that HN was a mistake. Surely the biggest source of stress in one's work should at least be something close to the core of the work. Whereas I was like someone who was in pain while running a marathon not from the exertion of running, but because I had a blister from an ill-fitting shoe. When I was dealing with some urgent problem during YC, there was about a 60% chance it had to do with HN, and a 40% chance it had do with everything else combined. [17] - -As well as HN, I wrote all of YC's internal software in Arc. But while I continued to work a good deal in Arc, I gradually stopped working on Arc, partly because I didn't have time to, and partly because it was a lot less attractive to mess around with the language now that we had all this infrastructure depending on it. So now my three projects were reduced to two: writing essays and working on YC. - -YC was different from other kinds of work I've done. Instead of deciding for myself what to work on, the problems came to me. Every 6 months there was a new batch of startups, and their problems, whatever they were, became our problems. It was very engaging work, because their problems were quite varied, and the good founders were very effective. If you were trying to learn the most you could about startups in the shortest possible time, you couldn't have picked a better way to do it. - -There were parts of the job I didn't like. Disputes between cofounders, figuring out when people were lying to us, fighting with people who maltreated the startups, and so on. But I worked hard even at the parts I didn't like. I was haunted by something Kevin Hale once said about companies: "No one works harder than the boss." He meant it both descriptively and prescriptively, and it was the second part that scared me. I wanted YC to be good, so if how hard I worked set the upper bound on how hard everyone else worked, I'd better work very hard. - -One day in 2010, when he was visiting California for interviews, Robert Morris did something astonishing: he offered me unsolicited advice. I can only remember him doing that once before. One day at Viaweb, when I was bent over double from a kidney stone, he suggested that it would be a good idea for him to take me to the hospital. That was what it took for Rtm to offer unsolicited advice. So I remember his exact words very clearly. "You know," he said, "you should make sure Y Combinator isn't the last cool thing you do." - -At the time I didn't understand what he meant, but gradually it dawned on me that he was saying I should quit. This seemed strange advice, because YC was doing great. But if there was one thing rarer than Rtm offering advice, it was Rtm being wrong. So this set me thinking. It was true that on my current trajectory, YC would be the last thing I did, because it was only taking up more of my attention. It had already eaten Arc, and was in the process of eating essays too. Either YC was my life's work or I'd have to leave eventually. And it wasn't, so I would. - -In the summer of 2012 my mother had a stroke, and the cause turned out to be a blood clot caused by colon cancer. The stroke destroyed her balance, and she was put in a nursing home, but she really wanted to get out of it and back to her house, and my sister and I were determined to help her do it. I used to fly up to Oregon to visit her regularly, and I had a lot of time to think on those flights. On one of them I realized I was ready to hand YC over to someone else. - -I asked Jessica if she wanted to be president, but she didn't, so we decided we'd try to recruit Sam Altman. We talked to Robert and Trevor and we agreed to make it a complete changing of the guard. Up till that point YC had been controlled by the original LLC we four had started. But we wanted YC to last for a long time, and to do that it couldn't be controlled by the founders. So if Sam said yes, we'd let him reorganize YC. Robert and I would retire, and Jessica and Trevor would become ordinary partners. - -When we asked Sam if he wanted to be president of YC, initially he said no. He wanted to start a startup to make nuclear reactors. But I kept at it, and in October 2013 he finally agreed. We decided he'd take over starting with the winter 2014 batch. For the rest of 2013 I left running YC more and more to Sam, partly so he could learn the job, and partly because I was focused on my mother, whose cancer had returned. - -She died on January 15, 2014. We knew this was coming, but it was still hard when it did. - -I kept working on YC till March, to help get that batch of startups through Demo Day, then I checked out pretty completely. (I still talk to alumni and to new startups working on things I'm interested in, but that only takes a few hours a week.) - -What should I do next? Rtm's advice hadn't included anything about that. I wanted to do something completely different, so I decided I'd paint. I wanted to see how good I could get if I really focused on it. So the day after I stopped working on YC, I started painting. I was rusty and it took a while to get back into shape, but it was at least completely engaging. [18] - -I spent most of the rest of 2014 painting. I'd never been able to work so uninterruptedly before, and I got to be better than I had been. Not good enough, but better. Then in November, right in the middle of a painting, I ran out of steam. Up till that point I'd always been curious to see how the painting I was working on would turn out, but suddenly finishing this one seemed like a chore. So I stopped working on it and cleaned my brushes and haven't painted since. So far anyway. - -I realize that sounds rather wimpy. But attention is a zero sum game. If you can choose what to work on, and you choose a project that's not the best one (or at least a good one) for you, then it's getting in the way of another project that is. And at 50 there was some opportunity cost to screwing around. - -I started writing essays again, and wrote a bunch of new ones over the next few months. I even wrote a couple that weren't about startups. Then in March 2015 I started working on Lisp again. - -The distinctive thing about Lisp is that its core is a language defined by writing an interpreter in itself. It wasn't originally intended as a programming language in the ordinary sense. It was meant to be a formal model of computation, an alternative to the Turing machine. If you want to write an interpreter for a language in itself, what's the minimum set of predefined operators you need? The Lisp that John McCarthy invented, or more accurately discovered, is an answer to that question. [19] - -McCarthy didn't realize this Lisp could even be used to program computers till his grad student Steve Russell suggested it. Russell translated McCarthy's interpreter into IBM 704 machine language, and from that point Lisp started also to be a programming language in the ordinary sense. But its origins as a model of computation gave it a power and elegance that other languages couldn't match. It was this that attracted me in college, though I didn't understand why at the time. - -McCarthy's 1960 Lisp did nothing more than interpret Lisp expressions. It was missing a lot of things you'd want in a programming language. So these had to be added, and when they were, they weren't defined using McCarthy's original axiomatic approach. That wouldn't have been feasible at the time. McCarthy tested his interpreter by hand-simulating the execution of programs. But it was already getting close to the limit of interpreters you could test that way — indeed, there was a bug in it that McCarthy had overlooked. To test a more complicated interpreter, you'd have had to run it, and computers then weren't powerful enough. - -Now they are, though. Now you could continue using McCarthy's axiomatic approach till you'd defined a complete programming language. And as long as every change you made to McCarthy's Lisp was a discoveredness-preserving transformation, you could, in principle, end up with a complete language that had this quality. Harder to do than to talk about, of course, but if it was possible in principle, why not try? So I decided to take a shot at it. It took 4 years, from March 26, 2015 to October 12, 2019. It was fortunate that I had a precisely defined goal, or it would have been hard to keep at it for so long. - -I wrote this new Lisp, called Bel, in itself in Arc. That may sound like a contradiction, but it's an indication of the sort of trickery I had to engage in to make this work. By means of an egregious collection of hacks I managed to make something close enough to an interpreter written in itself that could actually run. Not fast, but fast enough to test. - -I had to ban myself from writing essays during most of this time, or I'd never have finished. In late 2015 I spent 3 months writing essays, and when I went back to working on Bel I could barely understand the code. Not so much because it was badly written as because the problem is so convoluted. When you're working on an interpreter written in itself, it's hard to keep track of what's happening at what level, and errors can be practically encrypted by the time you get them. - -So I said no more essays till Bel was done. But I told few people about Bel while I was working on it. So for years it must have seemed that I was doing nothing, when in fact I was working harder than I'd ever worked on anything. Occasionally after wrestling for hours with some gruesome bug I'd check Twitter or HN and see someone asking "Does Paul Graham still code?" - -Working on Bel was hard but satisfying. I worked on it so intensively that at any given time I had a decent chunk of the code in my head and could write more there. I remember taking the boys to the coast on a sunny day in 2015 and figuring out how to deal with some problem involving continuations while I watched them play in the tide pools. It felt like I was doing life right. I remember that because I was slightly dismayed at how novel it felt. The good news is that I had more moments like this over the next few years. - -In the summer of 2016 we moved to England. We wanted our kids to see what it was like living in another country, and since I was a British citizen by birth, that seemed the obvious choice. We only meant to stay for a year, but we liked it so much that we still live there. So most of Bel was written in England. - -In the fall of 2019, Bel was finally finished. Like McCarthy's original Lisp, it's a spec rather than an implementation, although like McCarthy's Lisp it's a spec expressed as code. - -Now that I could write essays again, I wrote a bunch about topics I'd had stacked up. I kept writing essays through 2020, but I also started to think about other things I could work on. How should I choose what to do? Well, how had I chosen what to work on in the past? I wrote an essay for myself to answer that question, and I was surprised how long and messy the answer turned out to be. If this surprised me, who'd lived it, then I thought perhaps it would be interesting to other people, and encouraging to those with similarly messy lives. So I wrote a more detailed version for others to read, and this is the last sentence of it. - - - - - - - - - -Notes - -[1] My experience skipped a step in the evolution of computers: time-sharing machines with interactive OSes. I went straight from batch processing to microcomputers, which made microcomputers seem all the more exciting. - -[2] Italian words for abstract concepts can nearly always be predicted from their English cognates (except for occasional traps like polluzione). It's the everyday words that differ. So if you string together a lot of abstract concepts with a few simple verbs, you can make a little Italian go a long way. - -[3] I lived at Piazza San Felice 4, so my walk to the Accademia went straight down the spine of old Florence: past the Pitti, across the bridge, past Orsanmichele, between the Duomo and the Baptistery, and then up Via Ricasoli to Piazza San Marco. I saw Florence at street level in every possible condition, from empty dark winter evenings to sweltering summer days when the streets were packed with tourists. - -[4] You can of course paint people like still lives if you want to, and they're willing. That sort of portrait is arguably the apex of still life painting, though the long sitting does tend to produce pained expressions in the sitters. - -[5] Interleaf was one of many companies that had smart people and built impressive technology, and yet got crushed by Moore's Law. In the 1990s the exponential growth in the power of commodity (i.e. Intel) processors rolled up high-end, special-purpose hardware and software companies like a bulldozer. - -[6] The signature style seekers at RISD weren't specifically mercenary. In the art world, money and coolness are tightly coupled. Anything expensive comes to be seen as cool, and anything seen as cool will soon become equally expensive. - -[7] Technically the apartment wasn't rent-controlled but rent-stabilized, but this is a refinement only New Yorkers would know or care about. The point is that it was really cheap, less than half market price. - -[8] Most software you can launch as soon as it's done. But when the software is an online store builder and you're hosting the stores, if you don't have any users yet, that fact will be painfully obvious. So before we could launch publicly we had to launch privately, in the sense of recruiting an initial set of users and making sure they had decent-looking stores. - -[9] We'd had a code editor in Viaweb for users to define their own page styles. They didn't know it, but they were editing Lisp expressions underneath. But this wasn't an app editor, because the code ran when the merchants' sites were generated, not when shoppers visited them. - -[10] This was the first instance of what is now a familiar experience, and so was what happened next, when I read the comments and found they were full of angry people. How could I claim that Lisp was better than other languages? Weren't they all Turing complete? People who see the responses to essays I write sometimes tell me how sorry they feel for me, but I'm not exaggerating when I reply that it has always been like this, since the very beginning. It comes with the territory. An essay must tell readers things they don't already know, and some people dislike being told such things. - -[11] People put plenty of stuff on the internet in the 90s of course, but putting something online is not the same as publishing it online. Publishing online means you treat the online version as the (or at least a) primary version. - -[12] There is a general lesson here that our experience with Y Combinator also teaches: Customs continue to constrain you long after the restrictions that caused them have disappeared. Customary VC practice had once, like the customs about publishing essays, been based on real constraints. Startups had once been much more expensive to start, and proportionally rare. Now they could be cheap and common, but the VCs' customs still reflected the old world, just as customs about writing essays still reflected the constraints of the print era. - -Which in turn implies that people who are independent-minded (i.e. less influenced by custom) will have an advantage in fields affected by rapid change (where customs are more likely to be obsolete). - -Here's an interesting point, though: you can't always predict which fields will be affected by rapid change. Obviously software and venture capital will be, but who would have predicted that essay writing would be? - -[13] Y Combinator was not the original name. At first we were called Cambridge Seed. But we didn't want a regional name, in case someone copied us in Silicon Valley, so we renamed ourselves after one of the coolest tricks in the lambda calculus, the Y combinator. - -I picked orange as our color partly because it's the warmest, and partly because no VC used it. In 2005 all the VCs used staid colors like maroon, navy blue, and forest green, because they were trying to appeal to LPs, not founders. The YC logo itself is an inside joke: the Viaweb logo had been a white V on a red circle, so I made the YC logo a white Y on an orange square. - -[14] YC did become a fund for a couple years starting in 2009, because it was getting so big I could no longer afford to fund it personally. But after Heroku got bought we had enough money to go back to being self-funded. - -[15] I've never liked the term "deal flow," because it implies that the number of new startups at any given time is fixed. This is not only false, but it's the purpose of YC to falsify it, by causing startups to be founded that would not otherwise have existed. - -[16] She reports that they were all different shapes and sizes, because there was a run on air conditioners and she had to get whatever she could, but that they were all heavier than she could carry now. - -[17] Another problem with HN was a bizarre edge case that occurs when you both write essays and run a forum. When you run a forum, you're assumed to see if not every conversation, at least every conversation involving you. And when you write essays, people post highly imaginative misinterpretations of them on forums. Individually these two phenomena are tedious but bearable, but the combination is disastrous. You actually have to respond to the misinterpretations, because the assumption that you're present in the conversation means that not responding to any sufficiently upvoted misinterpretation reads as a tacit admission that it's correct. But that in turn encourages more; anyone who wants to pick a fight with you senses that now is their chance. - -[18] The worst thing about leaving YC was not working with Jessica anymore. We'd been working on YC almost the whole time we'd known each other, and we'd neither tried nor wanted to separate it from our personal lives, so leaving was like pulling up a deeply rooted tree. - -[19] One way to get more precise about the concept of invented vs discovered is to talk about space aliens. Any sufficiently advanced alien civilization would certainly know about the Pythagorean theorem, for example. I believe, though with less certainty, that they would also know about the Lisp in McCarthy's 1960 paper. - -But if so there's no reason to suppose that this is the limit of the language that might be known to them. Presumably aliens need numbers and errors and I/O too. So it seems likely there exists at least one path out of McCarthy's Lisp along which discoveredness is preserved. - - - -Thanks to Trevor Blackwell, John Collison, Patrick Collison, Daniel Gackle, Ralph Hazell, Jessica Livingston, Robert Morris, and Harj Taggar for reading drafts of this. diff --git a/examples/apps/explore_embeddings.ipynb b/examples/apps/explore_embeddings.ipynb deleted file mode 100644 index cb8a21a4a36..00000000000 --- a/examples/apps/explore_embeddings.ipynb +++ /dev/null @@ -1,383 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "d3f10990", - "metadata": {}, - "source": [ - "# Embedding explorer\n", - "\n", - "In this tutorial, we'll create a notebook-based Embeddings explorer. We will use functions, or **Weave Ops**, defined in the Weave ecosystem so that they're available to compatible types in the Weave UI.\n", - "\n", - "You'll learn how to:\n", - "- Save and version embeddings\n", - "- Build an interactive embeddings explorer using Weave\n", - "- Use the Weave ecosystem to: \n", - " - Embed a string column using OpenAI Embeddings\n", - " - Cluster & project the embeddings using umap\n", - "\n", - "Note: we will follow the clustering / projection approach from here: https://umap-learn.readthedocs.io/en/latest/clustering.html" - ] - }, - { - "cell_type": "markdown", - "id": "dd81428f", - "metadata": {}, - "source": [ - "## Import Dependencies\n", - "\n", - "Make sure you have weave installed." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "cb252cdb", - "metadata": {}, - "outputs": [], - "source": [ - "!pip install weave openai tiktoken tenacity hdbscan" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "13a3497f", - "metadata": {}, - "outputs": [], - "source": [ - "import pandas\n", - "import weave\n", - "from weave.legacy.ecosystem import openai\n", - "from weave.legacy.ecosystem import umap\n", - "from weave.legacy.ecosystem import hdbscan" - ] - }, - { - "cell_type": "markdown", - "id": "43771ee8", - "metadata": {}, - "source": [ - "## Download the data" - ] - }, - { - "cell_type": "markdown", - "id": "2c74303f", - "metadata": {}, - "source": [ - "We'll use a pokemon dataset from https://calmcode.io/datasets/pokemon.json." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a297c756", - "metadata": {}, - "outputs": [], - "source": [ - "!curl -O https://calmcode.io/datasets/pokemon.json" - ] - }, - { - "cell_type": "markdown", - "id": "81a36bc4", - "metadata": {}, - "source": [ - "## Read the data and save it to Weave" - ] - }, - { - "cell_type": "markdown", - "id": "a7b73421", - "metadata": {}, - "source": [ - "Weave will store and version the data locally under the name you pass as the second argument to `weave.save`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "e68f6c86", - "metadata": {}, - "outputs": [], - "source": [ - "raw_data = pandas.read_json('./pokemon.json')\n", - "data = weave.save(weave.legacy.ops.dataframe_to_arrow(raw_data), 'pokemon_data')" - ] - }, - { - "cell_type": "markdown", - "id": "8352ddb9", - "metadata": {}, - "source": [ - "We can show the data by executing it in a notebook, this will call `weave.show` on it." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "614f551d", - "metadata": {}, - "outputs": [], - "source": [ - "data # or weave.show(data)" - ] - }, - { - "attachments": { - "image.png": { - "image/png": "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" - } - }, - "cell_type": "markdown", - "id": "7619577f", - "metadata": {}, - "source": [ - "You can change the type of the displayed weave panel by changing `Table` to `Plot` and it will intelligently change to display a plot of the data.\n", - "![image.png](attachment:image.png)\n", - "\n", - "You've now created a Weave `Plot` Panel, or **Weave PanelPlot**. The default PanelPlot will show the HP (hit points or health points) of all the Pokemon versus their total stats. From the resulting PanelPlot, you can \n", - "* hover your mouse over a point to see the full data for it in the tooltip\n", - "* click on the magnifuing glass in the bottom right corner, then click and drag over a region of the plot to rescale and zoom to the selected region\n", - "* double-click on the plot area to reset to the original zoom level" - ] - }, - { - "cell_type": "markdown", - "id": "356dc2a9", - "metadata": {}, - "source": [ - "## Embed and cluster the data" - ] - }, - { - "cell_type": "markdown", - "id": "4fd47c57", - "metadata": {}, - "source": [ - "Now we can pass a column from this data to another Weave Op to embed it. This will turn each row in the column `name` into a vector." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "43cec2d8", - "metadata": {}, - "outputs": [], - "source": [ - "embeddings = openai.openai_embed(data['name'], {\"model\": \"text-embedding-ada-002\"})" - ] - }, - { - "cell_type": "markdown", - "id": "6715de4a", - "metadata": {}, - "source": [ - "Now, we'll project this to 2 dimensions for clustering, and then we'll cluster this projection using `hdbscan`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5eff5bca", - "metadata": {}, - "outputs": [], - "source": [ - "clusterable_projection = umap.umap_project(\n", - " embeddings, {\n", - " 'n_neighbors': 30,\n", - " 'min_dist': 0,\n", - " 'n_components': 2,\n", - " }\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "583830d0", - "metadata": {}, - "outputs": [], - "source": [ - "clusters = hdbscan.hdbscan_cluster(clusterable_projection, {\n", - " 'min_samples': 10,\n", - " 'min_cluster_size': 60\n", - " })" - ] - }, - { - "cell_type": "markdown", - "id": "87c4ad19", - "metadata": {}, - "source": [ - "Then, we'll project the embeddings again for plotting. For efficient computation, Weave isn't actually executing anything until we call `weave.use` or `weave.show` on the built-up computation (or \"compute graph\")." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "21f5917b", - "metadata": {}, - "outputs": [], - "source": [ - "projection = umap.umap_project(embeddings, {})" - ] - }, - { - "cell_type": "markdown", - "id": "e3b7f76c", - "metadata": {}, - "source": [ - "## Combine data for plotting" - ] - }, - { - "cell_type": "markdown", - "id": "1d017836", - "metadata": {}, - "source": [ - "Finally, we'll combine our data into one big table so we can display the Pokemon name and types alongside our embedding. Here, we're calling `weave.use` on each weave object to execute the computation, and then we're merging them into a new list of dictionaries. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d642110d", - "metadata": {}, - "outputs": [], - "source": [ - "weave.show([{'x': x, 'y': y, 'k':k, 'd':d} for (x,y),k,d in \n", - " zip(weave.use(projection),\n", - " weave.use(clusters),\n", - " weave.use(data))])" - ] - }, - { - "cell_type": "markdown", - "id": "1cf9b267", - "metadata": {}, - "source": [ - "## Build and publish an interactive dashboard" - ] - }, - { - "cell_type": "markdown", - "id": "202f5f66-a3c4-41ea-b1ca-cef26f98f26b", - "metadata": {}, - "source": [ - "Some Weave UI tips to build an interactive dashboard in-line to explore and get insights into your embeddings:\n", - "* **add new panels**: click the + button on the drawer that pops out on the right hand side of the cell\n", - "* **change panel types**: as we did above to change the `Table` panel named `pokemon_data` into a `Plot` panel\n", - "* **refer to other panels by their name**: add a new `plot` panel that uses `table.selected_data` from the first panel. You can click the pencil to edit the configuration of your `plot`. " - ] - }, - { - "attachments": { - "image.png": { - "image/png": "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Y29tL2V4aWYvMS4wLyIKICAgICAgICAgICAgeG1sbnM6dGlmZj0iaHR0cDovL25zLmFkb2JlLmNvbS90aWZmLzEuMC8iPgogICAgICAgICA8ZXhpZjpVc2VyQ29tbWVudD5TY3JlZW5zaG90PC9leGlmOlVzZXJDb21tZW50PgogICAgICAgICA8ZXhpZjpQaXhlbFhEaW1lbnNpb24+MTY3MDwvZXhpZjpQaXhlbFhEaW1lbnNpb24+CiAgICAgICAgIDxleGlmOlBpeGVsWURpbWVuc2lvbj43MDQ8L2V4aWY6UGl4ZWxZRGltZW5zaW9uPgogICAgICAgICA8dGlmZjpSZXNvbHV0aW9uVW5pdD4yPC90aWZmOlJlc29sdXRpb25Vbml0PgogICAgICAgICA8dGlmZjpYUmVzb2x1dGlvbj4xNDQvMTwvdGlmZjpYUmVzb2x1dGlvbj4KICAgICAgICAgPHRpZmY6WVJlc29sdXRpb24+MTQ0LzE8L3RpZmY6WVJlc29sdXRpb24+CiAgICAgICAgIDx0aWZmOk9yaWVudGF0aW9uPjE8L3RpZmY6T3JpZW50YXRpb24+CiAgICAgIDwvcmRmOkRlc2NyaXB0aW9uPgogICA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgpZQ7xWAABAAElEQVR4AeydB2BUVfbGT3ovkEoJhN57B1FAEHsHe/evrrqra9t1177uuuvaXdvae1lRERRBivTeewuhJZDee8L/fDe8YZJMwkxISPuODvPmlfvu+81M5r77neJ2TE1oJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACzZ6Ae7O/Ql4gCZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZCAIUBhiB8EEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEmghBCgMtZA3mpdJAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAhSG+BkgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIggRZCgMJQC3mjeZkkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQGGInwESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESaCEEKAy1kDeal0kCJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACnq4gSE9Pl7y8PFcO4b4kQAIkQAIkQAIkcMoEMo+5ya4St1Nuhw2QAAmQAAmQAAmQgKsEov19pKt7mTksPDzc1cO5PwmQAAmQAAmQAAk0OgIuCUMlJSVSWFjY6C6CHSIBEiABEiABEmjeBHLK3CSpmMJQ836XeXUkQAIkQAIk0DgJ+Hl4SJFHSePsHHtFAiRAAiRAAiRAArUg4JIwZLXfuXNna5HPJEACJEACJEACJFCvBOLi4uq1fTZOAiRAAiRAAiRAAiRAAiRAAiRAAiRAAi2JQK2EoZYEiNdKAiRAAiRAAiTQuAh0DPSXyTGRjatT7A0JkAAJkAAJkECzIrA/J09mH0xqVtfEiyEBEiABEiABEiABi4C7tcBnEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiCB5k2AwlDzfn95dSRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRgI0BhyIaCCyRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiTQvAlQGGre7y+vjgRIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARsBCgM2VBwgQRIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgASaNwEKQ837/eXVkQAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkICNAIUhGwoukAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkEDzJkBhqHm/v7w6EiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABErARoDBkQ8EFEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEmjeBCgMNe/3l1dHAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAjYCFIZsKLhAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAs2bAIWh5v3+8upIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIwEaAwpANBRdIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIoHkToDDUvN9fXh0JkAAJkAAJkAAJkAAJ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t1Xtd333i+2TAAmQAAnULQFXRCHrzJY4hHRyR5OSTHo5pJlraGuyNYYaGhzPTwIkQAIkQAIkcHoINKYaQ6gT+Mgjj8jdd999ei6eZyEBEiABEiABEmgQAo2lxlDli7evOXTu5EmCYtsNaaaOYWamoHZQeFiYSZvjbH+Q7i1dRZtQFYVQm8cVQ72htLQ0KTtWJiHBIZri90ThcrRToBNuqamp4u3lrXWjWzlVM9qV89e0L5hkKhPUTEINxqAg1iCuiRe3kQAJkEBTIXBEU6b+oLXwkF5+zOhRMnBAf5e6bl9zKDoq0jhRIOq0oaxiTG9D9YLnJQESIAESIAESIIFGTCBYvV+HDRsmGRkZ0r59+0bcU3aNBEiABEiABEigOROwTyu3cuWqBheGMKGFaB88XDVTD6idn6uHmf0RhdOmTXS1x/pqXYd2bdtWu70+N4AJxC48aCRAAiRAAs2HAKJ9ykrLaiUKgYIVOTRj5s+SkpIqRUVFLjlU1DVJCkN1TZTtkQAJkAAJkAAJNDsCPXv2lDfffLPZXRcviARIgARIgARIoOkRgDgUGBQknh4eTa/z7DEJkAAJkAAJNFEC/bXOXdcunTXKtWKUqiuXA3EI6VYLCgoaVBRCnykMufLOcV8SIAESIAESIAESIAESIAESIAESIAESaGACsR07NHAPeHoSIAESIAESaFkEEBF6KqKQRcvd3V38/V1LoWodW5fP7nXZGNsiARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARJovAQoDDXe94Y9IwESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIIE6JUBhqE5xsjESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESaLwEKAw13veGPSMBEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiCBOiVAYahOcbIxEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEmi8BCgMNd73hj0jARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIggTolQGGoTnGyMRIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARJovAQoDDXe94Y9IwESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIIE6JUBhqE5xsjESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESaLwEPBtv19gzEiABEiABEiABEjhBINz9mAz0OiZSnCNxcTknNnCJBEiABEiABEiABOqBwEAvN9lQ7FYPLbNJEiABEiABEiABEmhYArUShuLi4hq21zw7CZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZCAywRqJQy5fBYeQAIkQAIkQAIkQAKnSCClzE3mFrpJx0B/mRwTeYqt8XASIAESIAESIAESqJ7A/pw82XAwqfoduIUESIAESIAESIAEmjCBWglDnTt3bsKXzK6TAAmQAAmQAAk0JQKMVG5K7xb7SgIkQAIkQAIkQAIkQAIkQAIkQAIk0NgJuDf2DrJ/JEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACdUOAwlDdcGQrJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJNDoCVAYavRvETtIAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAnVDgMJQ3XBkKyRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiTQ6AlQGGr0bxE7SAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAJ1Q4DCUN1wZCskQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIk0OgJUBhq9G8RO0gCJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACdUOAwlDdcGQrJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJNDoCVAYavRvETtIAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAnVDgMJQ3XBkKyRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiTQ6Al4NvoesoMkQAIkQAIkQAIk0EwIlJaWSkpKiqxdu17mzp0r/v7+8uCDf5RWrVqZKywsLJStW7fJ9z9Mly1btkhOTo4MGjRQrpo6RQYMGCCenhy6NZOPAi+DBEiABEiABEiABEiABEiABEiABBqMQIufXTh27JgUl5RImU7U+Pj4iJubm8M3o6ysTEp0P5i3t7fDfbiSBEiABEiABEiABGoikJubKytWrpJv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AZg7Z9bgHfQgGllwcGsOqMLCImRkZEouzqo+HZ32p/1q/2pBQUF92j87IwESIAESaEuAHkNtefAVCZAACZAACZAACRgCgQEBWH7DjTh65Bj+z//8D15fuw6JiWPgMGoU0tIvIj09HeMkhvyKW26Gu4R5oZEACZAACZAACZAACQxeArm5eWi5PLxRcj+XOCYBixYugKOj4+Ad9CAamYYcDA0NRZ54XZWXl5tHfw0vNDSkxyEO+2ss7JcESIAEhisBCkPD9cryvEiABEiABEiABK6JgE4YjBfh579++J9Yu+4NvPHGG9i2fbvpU8PG3Xbbajzx+GNISkq6puOwMQmQAAmQAAmQAAkMZwKLFi3EgvnzzSk6Og5c4Jro6Cjcv+YeaLhgf3+/fs2TM1yvZ1BQINzd3SScXImEWq5BfX1Dn52qi4sz3NzcobmiPD09+6xfdkQCJEACJGCfAIUh+1xYSgIkQAIkQAIkQAKGgK5Y/PKXnsEXPv8USkpKoHmF/P39oYmXVTyikQAJkAAJkAAJkAAJdE7A0cEB+hhoc5AxBAUGDvQwhvzxVbShcDPkLyNPgARIgARAYYhvAhIgARIgARIgARK4CgEVgDQpcVhY2FVqcjcJkAAJkAAJkAAJkAAJkAAJkAAJkAAJDG4CA79kY3Dz4ehIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIYNgQoDA0bC4lT4QESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAEuiZAYahrPtxLAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAsOGAIWhYXMpeSIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIk0DUBCkNd8+FeEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEhg2BCgMDZtLyRMhARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgga4JOHW9m3ubm5uxb/9+bNr0ESoqKjFmzGg8+MD98PX1JRwSIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESGFIE6DF0lcv17ob38M1vfgtZmVnw8fHGu+9uwP/89OdXacXdJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJDD4CNBjqItrUlZWju/927/j0YcfFnHoOTg4OKCktBROTsTWBTbuIgESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESGKQEqHB0cWH+8corcHJ0wuOPf86IQlrV38+vixbcRQIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAKDlwCFoS6uzeHDhzFp4kQ4Ojpg9+49qG+oR1xsHKJjouEwalQXLbmLBEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABAYfAQpDXVyTjIwshIQE49e//i0qKiuQmZkNV1cXfO2rX8GMGdPbtNy9ezcqKyvblFleNDU1mc2amhoUFhZaivncTQIWfsq3vr6+m61YrT2BhoYGU8T3YHsy3Xvd3NxMft1D1Wkty99yVVXVgPwt19XVdTo27iABEiABEiABEiABEiABEiABEiABEiABEiCBkUKAwlAnV7qlpcWIQaWlJbj33nswcUKyCD9V+I/v/wA/+e//g3VrX2vT8vTp0ygoKGhT1v6Fihrl5eXti/m6mwRqa2uhD9q1EeB7kPyujcC1t1aRXB8DYZorjkYCJEACJEACJEACJEACJEACJEACJEACJEACI5kAhaFOrv4oCRUXEhyMuLg4rFp5q4STczQ1ly1dip//8pcyqVkLd3c3a+uHH34YKibZM12l/uKLL8LHx8f0Z68OyzonoGJQbm4uAgICDMPOa3JPVwSUobLU9zSt5wQyMjKgf+Ex0dE9b8wWhoDlbzkwMBDe3t7XnYqK9wMlSF33k+UBSYAESIAESIAESIAESIAESIAESIAESIAESKATAhSGOgGjxYljEnHy1Mk2gk+wiEWODo5obNSwXFeEIVdX1y56at2lYhNXq18VU4cKFmb6bNnuUIkF3SZAht1G1aGiZhYjvw5Yul2gn4FqA/VZaDl+twfMiiRAAiRAAiRAAiRAAiRAAiRAAoaAJTw9cZAACZDA9STg7Ox8PQ83oo7FmDpdXO41a+5FRkYmPtq82YSAy8/Px85duzBlypQBWe3exVC5iwRIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgASuSoAeQ10gmj59Gp584nH88Y9/ws6du1FRUQ4Vh55//htdtOIuEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEhicBCgMdXFdNGTUF7/4eUyeMgkXL16Ck+QZmjhpIiZPmtRFK+4iARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIggcFJgMLQVa6Lu7s7li1diqamJlPTyYnIroKMu0mABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABAYpAaoc3bgwmrCcglA3QLEKCZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZDAoCbgMKhHx8GRAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAn0GQEKQ32Gkh2RAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQwOAmQGFocF8fjo4ESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAE+owAhaE+Q8mOSIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESGBwE6AwNLivD0dHAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAn1GgMJQn6FkRyRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiQwuAlQGBrc14ejIwESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIIE+I+DUZz2xIxIgARIgARIgARIggT4j0NzcjIqKShQUFMDNzRVhYWFwcuKtW58BZkckQAIkQAIkQAIkQAIkQAIkQAIkMEIJ0GNohF54njYJkAAJkAAJkMDgJlBRUYHNmzfjP77/A7y+dp2IRBWDe8AcHQmQAAmQAAmQAAmQAAmQAAmQAAmQwJAgwGWnQ+IycZAkQAIkQAIkQAIjiUBjYyNSU1Px4aaPkJmZhcTEMWhqahpJCHiuJEACJEACJEACJEACJDDgBGpr6/D+xg86HcecObMQFRnZYf/uPfvQ0tKCWTNnmH37DxzEqFGjMG/u7A51r1Zw7P+zdx/gcV3ngfdfYDAVvTcWgL03sVMkJVnNkizJjiXHJYos2/LGibNer+PYcZyN7c9Z50m8jr2bxLHTY8exZMmSVaguqrCKvYkNBEACRCF6H9TvvBecwQCYIQpnQGDwP88znDvnnnvuPb87AMl55z2noka8XT2yMj9THDa+4x/MazzeR48el/NFRcG6k/j4eLn7rjuC7qMSgWgRIDAULXeScSCAAAIIIIBA1AhU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JEACJEACJEACJEACJEACJEACJEACJEACJEAC3kaANYa87Yhyf0iABEiABEiABEiABEiABEiABEiABEjAwwj07t1LevboYXrt7884Zg87fOwuCZAACZCAhxGgY8jDDhi7SwIkQAIkQAIkQAIkQAIkQAIkQAIkQALeRsDfz0/wopEACZAACZAACbieAK+4rmfMFkiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEjALQjQMeQWh4GdIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAHXE6BjyPWM2QIJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJuAUBOobc4jCwEyRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiTgegIBrm+CLZAACZAACZAACZCA8wicSM+Qj/Yfd94GuSUSIAESIAESIAESIAESIAESIAESIAES8CECt+QYiouL8yFE3FUSIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAES8A4ClJLzjuPIvSABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiCBYglU+EGt2KW4AAmQAAmQAAmQAAmQAAmQAAmQAAmQAAn4KIH4+Hiz5+Hh4T5KgLvt6wSys7N9HQH3nwRI4DYQqFix4m1o1TeavCUpOd9Aw70kARIgARIgARIgARIgARIgARIgARIgARIgARLg4Cx/AyRAAiTgXQSYMeRdx5N7QwIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIOCbDGkEM0nEECJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEAC3kWAjiHvOp7cGxIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARJwSICOIYdoOIMESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAEvIsAHUPedTy5NyRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiTgkAAdQw7RcAYJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJeBcBOoa863hyb0iABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEjAIQE6hhyi4QwSIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAES8C4CdAx51/Hk3pAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZCAQwJ0DDlEwxkkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIk4F0E6BjyruPJvSEBEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABhwToGHKIhjNIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIwLsI0DHkXceTe0MCJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACDgnQMeQQDWeQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQgHcRoGPIu44n94YESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAEHBKgY8ghGs4gARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgAe8i8P/kFu9JFZ12fgAAAABJRU5ErkJggg==" - } - }, - "cell_type": "markdown", - "id": "0cf240cf", - "metadata": {}, - "source": [ - "![image.png](attachment:image.png)" - ] - }, - { - "cell_type": "markdown", - "id": "4b9fc573", - "metadata": {}, - "source": [ - "To share your creation, you can click \"open in new tab\" in the right-hand drawer, and then click \"Publish\" to save it to wandb." - ] - }, - { - "cell_type": "markdown", - "id": "9a4092e3", - "metadata": {}, - "source": [ - "### Bonus: Weaving everything together" - ] - }, - { - "cell_type": "markdown", - "id": "4f211f9e", - "metadata": {}, - "source": [ - "Here's a **Weave Board** for all these panels, defined in code. We're actively working on additional examples and tutorials to explain these concepts in more detail. To inspire some initial exploration, you can:\n", - "* **open the panel in a new window** from the right-hand drawer on hover, to view a full-page Weave Board\n", - "* **see the details of selected data** by clicking the mouse button in the bottom right of the PanelPlot, then clicking and dragging to select a subregion of points\n", - "* **customize the Board** by adding, changing, and interconnecting panels\n", - "* **save and share your work** by renaming the Board and publishing it to W&B" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "e194b3ce", - "metadata": {}, - "outputs": [], - "source": [ - "weave.legacy.panels.Board(\n", - " vars={\n", - " 'data': data,\n", - " 'limit': 1005,\n", - " 'limited_data': lambda data, limit: data.limit(limit),\n", - " 'embeddings': lambda limited_data: openai.openai_embed(limited_data['name'], {\"model\": \"text-embedding-ada-002\"}),\n", - " 'clusterable_projection': lambda embeddings: umap.umap_project(\n", - " embeddings, {\n", - " 'n_neighbors': 30,\n", - " 'min_dist': 0,\n", - " 'n_components': 2,\n", - " }\n", - " ),\n", - " 'clusters': lambda clusterable_projection: hdbscan.hdbscan_cluster(clusterable_projection, {\n", - " 'min_samples': 10,\n", - " 'min_cluster_size': 50\n", - " }),\n", - " 'projection': lambda embeddings: umap.umap_project(embeddings, {})\n", - " },\n", - " panels=[\n", - " weave.legacy.panels.BoardPanel(\n", - " lambda limited_data, projection, clusters: weave.legacy.panels.Plot(\n", - " limited_data,\n", - " x=lambda row, index: projection[index][0],\n", - " y=lambda row, index: projection[index][1],\n", - " label=lambda row, index: clusters[index],\n", - " ),\n", - " id='projection_plot',\n", - " layout=weave.legacy.panels.BoardPanelLayout(x=0, y=0, w=24, h=12)\n", - " ),\n", - " weave.legacy.panels.BoardPanel(\n", - " lambda projection_plot: projection_plot.selected_data(),\n", - " layout=weave.legacy.panels.BoardPanelLayout(x=0, y=0, w=24, h=12)\n", - " )\n", - " ]\n", - ")" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/experimental/AutoBoard.ipynb b/examples/experimental/AutoBoard.ipynb deleted file mode 100644 index 2a75baac7b2..00000000000 --- a/examples/experimental/AutoBoard.ipynb +++ /dev/null @@ -1,91 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "315969ff", - "metadata": {}, - "outputs": [], - "source": [ - "import weave\n", - "from weave.legacy.scripts import syndata\n", - "from weave.legacy.scripts import syndata_mon\n", - "from weave.legacy.panels_py import panel_autoboard" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a6b988de", - "metadata": {}, - "outputs": [], - "source": [ - "predictions = weave.save(syndata_mon.random_predictions(10), 'predictions')\n", - "#predictions" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "64aef598", - "metadata": {}, - "outputs": [], - "source": [ - "panel_autoboard.auto_panels(predictions)\n", - "#weave.legacy.ops.project('shawn', 'fasion-sweep').runs().history().concat()['_step'].type" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "78a3a155", - "metadata": {}, - "outputs": [], - "source": [ - "metrics = weave.save(syndata.random_metrics(10000, 10), 'syndata')\n", - "#metrics" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "77e03fd6", - "metadata": {}, - "outputs": [], - "source": [ - "#panel_autoboard.auto_panels(metrics)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8b417fd2", - "metadata": {}, - "outputs": [], - "source": [ - "#panel_autoboard.auto_panels(weave.legacy.ops.project('shawn', 'oai-api5').run('test4').history2())" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/experimental/Monitor.ipynb b/examples/experimental/Monitor.ipynb deleted file mode 100644 index fee2766ae24..00000000000 --- a/examples/experimental/Monitor.ipynb +++ /dev/null @@ -1,219 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "f7512dfa", - "metadata": {}, - "source": [ - "# Model monitoring dashboard example\n", - "\n", - "This notebook currently requires the plotly library to be installed.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "039f298a", - "metadata": {}, - "outputs": [], - "source": [ - "import pandas as pd\n", - "import weave\n", - "# Weave package now defaults to eager mode, but lazy mode required for this example notebook for now.\n", - "weave.use_lazy_execution()\n", - "from weave.legacy.scripts import syndata_mon\n", - "from weave.legacy.arrow.list_ import dataframe_to_arrow\n", - "from weave.legacy.ecosystem import wandb" - ] - }, - { - "cell_type": "markdown", - "id": "8519889f", - "metadata": {}, - "source": [ - "## Create synthetic data\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "29f45c3d", - "metadata": {}, - "outputs": [], - "source": [ - "data = syndata_mon.random_predictions(10)\n", - "timestamps = data._arrow_data.field('timestamp')\n", - "start_date = timestamps[0].as_py().to_pydatetime()\n", - "end_date = timestamps[-1].as_py().to_pydatetime()\n", - "predictions = weave.save(data, 'predictions')" - ] - }, - { - "cell_type": "markdown", - "id": "cdd59e39", - "metadata": {}, - "source": [ - "## Create a Weave Board for the data\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8049b6dd", - "metadata": {}, - "outputs": [], - "source": [ - "# Create a dashboard for our data\n", - "board = weave.legacy.panels.Board(\n", - " vars={\n", - " 'x_max': weave.legacy.ops.date_parse(str(end_date)),\n", - " 'x_min': lambda x_max: x_max - weave.legacy.ops.days(5),\n", - " 'predictions_query': weave.legacy.panels.Query(\n", - " predictions,\n", - " conditions=[\n", - " lambda query_input: weave.legacy.panels.QueryCondition(\n", - " expression=query_input['model_version'],\n", - " editor=weave.legacy.panels.SelectEditor(\n", - " choices=query_input['model_version']\n", - " )\n", - " ),\n", - " lambda query_input: weave.legacy.panels.QueryCondition(\n", - " expression=query_input['username'],\n", - " editor=weave.legacy.panels.SelectEditor(\n", - " choices=query_input['username']\n", - " )\n", - " ),\n", - " ]),\n", - " 'predictions': lambda predictions_query: predictions_query.selected()\n", - " },\n", - " panels=[\n", - " weave.legacy.panels.BoardPanel(\n", - " lambda predictions, x_min, x_max: wandb.TimeSeries(\n", - " predictions,\n", - " x=lambda item: item[\"timestamp\"],\n", - " label=lambda item: item[\"model_version\"],\n", - " agg=lambda preds: preds.count(),\n", - " min_x=x_min,\n", - " max_x=x_max,\n", - " mark=\"bar\",\n", - " axis_labels={\"y\": \"num_preds\"},\n", - " ),\n", - " layout=weave.legacy.panels.BoardPanelLayout(x=0, y=0, w=12, h=6)\n", - " ),\n", - " weave.legacy.panels.BoardPanel(\n", - " lambda predictions, x_min, x_max: wandb.TimeSeries(\n", - " predictions,\n", - " x=lambda item: item[\"timestamp\"],\n", - " label=lambda item: item[\"username\"],\n", - " agg=lambda preds: preds.count(),\n", - " min_x=x_min,\n", - " max_x=x_max,\n", - " mark=\"bar\",\n", - " axis_labels={\"y\": \"num_preds\"},\n", - " ),\n", - " layout=weave.legacy.panels.BoardPanelLayout(x=12, y=0, w=12, h=6)\n", - " ),\n", - " weave.legacy.panels.BoardPanel(\n", - " lambda predictions, x_min, x_max: wandb.TimeSeries(\n", - " predictions,\n", - " x=lambda item: item[\"timestamp\"],\n", - " #label=lambda item: item[\"model_version\"],\n", - " agg=lambda preds: preds[\"api_cost\"].sum(),\n", - " min_x=x_min,\n", - " max_x=x_max,\n", - " mark=\"line\",\n", - " axis_labels={\"y\": \"api_cost\"},\n", - " ),\n", - " layout=weave.legacy.panels.BoardPanelLayout(x=0, y=6, w=8, h=6)\n", - " ),\n", - " weave.legacy.panels.BoardPanel(\n", - " lambda predictions, x_min, x_max: wandb.TimeSeries(\n", - " predictions,\n", - " x=lambda item: item[\"timestamp\"],\n", - " #label=lambda item: item[\"model_version\"],\n", - " agg=lambda preds: preds[\"prompt_tokens\"].sum() + preds['completion_tokens'].sum(),\n", - " min_x=x_min,\n", - " max_x=x_max,\n", - " mark=\"line\",\n", - " axis_labels={\"y\": \"total_tokens\"},\n", - " ),\n", - " layout=weave.legacy.panels.BoardPanelLayout(x=8, y=6, w=8, h=6)\n", - " ),\n", - " weave.legacy.panels.BoardPanel(\n", - " lambda predictions, x_min, x_max: wandb.TimeSeries(\n", - " predictions,\n", - " x=lambda item: item[\"timestamp\"],\n", - " #label=lambda item: item[\"model_version\"],\n", - " agg=lambda preds: preds[\"latency\"].avg(),\n", - " min_x=x_min,\n", - " max_x=x_max,\n", - " mark=\"line\",\n", - " axis_labels={\"y\": \"avg_latency\"},\n", - " ),\n", - " layout=weave.legacy.panels.BoardPanelLayout(x=16, y=6, w=8, h=6)\n", - " ),\n", - " weave.legacy.panels.BoardPanel(\n", - " lambda predictions, x_min, x_max: wandb.Distribution(\n", - " predictions,\n", - " value_fn=lambda pred: pred['api_cost'],\n", - " bin_size=0.2\n", - " ),\n", - " layout=weave.legacy.panels.BoardPanelLayout(x=0, y=12, w=8, h=6)\n", - " ),\n", - " weave.legacy.panels.BoardPanel(\n", - " lambda predictions, x_min, x_max: wandb.Distribution(\n", - " predictions,\n", - " value_fn=lambda pred: pred['prompt_tokens'] + pred['completion_tokens'],\n", - " bin_size=25\n", - " ),\n", - " layout=weave.legacy.panels.BoardPanelLayout(x=8, y=12, w=8, h=6)\n", - " ),\n", - " weave.legacy.panels.BoardPanel(\n", - " lambda predictions, x_min, x_max: wandb.Distribution(\n", - " predictions,\n", - " value_fn=lambda pred: pred['latency'],\n", - " bin_size=0.05\n", - " ),\n", - " layout=weave.legacy.panels.BoardPanelLayout(x=16, y=12, w=8, h=6)\n", - " ),\n", - " weave.legacy.panels.BoardPanel(\n", - " lambda predictions: predictions,\n", - " layout=weave.legacy.panels.BoardPanelLayout(x=0, y=18, w=24, h=12)\n", - " )\n", - " ]\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "1d8bafc9", - "metadata": {}, - "outputs": [], - "source": [ - "board" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/experimental/MonitorPanelPlot.ipynb b/examples/experimental/MonitorPanelPlot.ipynb deleted file mode 100644 index 647bc0de53b..00000000000 --- a/examples/experimental/MonitorPanelPlot.ipynb +++ /dev/null @@ -1,252 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "f7512dfa", - "metadata": {}, - "source": [ - "# Model monitoring dashboard example\n", - "\n", - "This notebook currently requires the plotly library to be installed.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3e7d3247", - "metadata": {}, - "outputs": [], - "source": [ - "import weave\n", - "import weave.legacy.panels\n", - "weave.use_lazy_execution()\n", - "from weave.legacy.scripts import syndata_mon" - ] - }, - { - "cell_type": "markdown", - "id": "8519889f", - "metadata": {}, - "source": [ - "## Create synthetic data\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d6a3a6f1", - "metadata": {}, - "outputs": [], - "source": [ - "data = syndata_mon.random_predictions(10)\n", - "timestamps = data._arrow_data.field('timestamp')\n", - "start_date = timestamps[0].as_py().to_pydatetime()\n", - "end_date = timestamps[-1].as_py().to_pydatetime()\n", - "predictions = weave.save(data, 'predictions')" - ] - }, - { - "cell_type": "markdown", - "id": "cdd59e39", - "metadata": {}, - "source": [ - "## Create a Weave Board for the data\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "57fc86e0", - "metadata": {}, - "outputs": [], - "source": [ - "# Fetch the data by the latest alias instead of its version hash.\n", - "# TODO: change weave.save to do this automatically\n", - "predictions = weave.legacy.ops.get(\"local-artifact:///predictions:latest/obj\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "25c63bf1-10f4-4a9b-b5f3-01361126bc2c", - "metadata": {}, - "outputs": [], - "source": [ - "# from weave.legacy import context_state\n", - "# context_state._eager_mode.get()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8049b6dd", - "metadata": {}, - "outputs": [], - "source": [ - "# Create a dashboard for our data\n", - "board = weave.legacy.panels.Board(\n", - " vars={\n", - " 'data': predictions,\n", - " 'zoom_range': weave.legacy.ops.make_list(\n", - " a=weave.legacy.ops.date_parse(str(start_date)),\n", - " b=weave.legacy.ops.date_parse(str(end_date))),\n", - " 'data_range': lambda data: weave.legacy.ops.make_list(\n", - " a=data['timestamp'].min(),\n", - " b=data['timestamp'].max()\n", - " ),\n", - " 'bin_range': lambda zoom_range, data_range: zoom_range.coalesce(data_range),\n", - " 'predictions_query': weave.legacy.panels.Query(\n", - " predictions,\n", - " conditions=[\n", - " lambda query_input: weave.legacy.panels.QueryCondition(\n", - " expression=query_input['model_version'],\n", - " editor=weave.legacy.panels.SelectEditor(\n", - " choices=query_input['model_version']\n", - " )\n", - " ),\n", - " lambda query_input: weave.legacy.panels.QueryCondition(\n", - " expression=query_input['username'],\n", - " editor=weave.legacy.panels.SelectEditor(\n", - " choices=query_input['username']\n", - " )\n", - " ),\n", - " ]),\n", - " 'predictions': lambda predictions_query: predictions_query.selected()\n", - " },\n", - " panels=[\n", - " weave.legacy.panels.BoardPanel(\n", - " lambda predictions, zoom_range, bin_range: weave.legacy.panels.Plot(\n", - " predictions,\n", - " x=lambda item: item[\"timestamp\"].bin(\n", - " weave.legacy.ops.timestamp_bins_nice(bin_range, 30)),\n", - " label=lambda item: item[\"model_version\"],\n", - " y=lambda preds: preds.count(),\n", - " groupby_dims=['x', 'label'],\n", - " domain_x=zoom_range,\n", - " mark=\"bar\",\n", - " ),\n", - " layout=weave.legacy.panels.BoardPanelLayout(x=0, y=0, w=12, h=6)\n", - " ),\n", - " weave.legacy.panels.BoardPanel(\n", - " lambda predictions, zoom_range, bin_range: weave.legacy.panels.Plot(\n", - " predictions,\n", - " x=lambda item: item[\"timestamp\"].bin(\n", - " weave.legacy.ops.timestamp_bins_nice(bin_range, 100)),\n", - " label=lambda item: item[\"username\"],\n", - " y=lambda preds: preds.count(),\n", - " groupby_dims=['x', 'label'],\n", - " domain_x=zoom_range,\n", - " mark=\"bar\",\n", - " ),\n", - " layout=weave.legacy.panels.BoardPanelLayout(x=12, y=0, w=12, h=6)\n", - " ),\n", - " weave.legacy.panels.BoardPanel(\n", - " lambda predictions, zoom_range, bin_range: weave.legacy.panels.Plot(\n", - " predictions,\n", - " x=lambda item: item[\"timestamp\"].bin(\n", - " weave.legacy.ops.timestamp_bins_nice(bin_range, 100))['start'],\n", - " y=lambda preds: preds[\"api_cost\"].sum(),\n", - " groupby_dims=['x'],\n", - " domain_x=zoom_range,\n", - " mark=\"line\",\n", - " ),\n", - " layout=weave.legacy.panels.BoardPanelLayout(x=0, y=6, w=8, h=6)\n", - " ),\n", - " weave.legacy.panels.BoardPanel(\n", - " lambda predictions, zoom_range, bin_range: weave.legacy.panels.Plot(\n", - " predictions,\n", - " x=lambda item: item[\"timestamp\"].bin(\n", - " weave.legacy.ops.timestamp_bins_nice(bin_range, 100))['start'],\n", - " y=lambda preds: preds[\"prompt_tokens\"].sum() + preds['completion_tokens'].sum(),\n", - " groupby_dims=['x'],\n", - " domain_x=zoom_range,\n", - " mark=\"line\",\n", - " ),\n", - " layout=weave.legacy.panels.BoardPanelLayout(x=8, y=6, w=8, h=6)\n", - " ),\n", - " weave.legacy.panels.BoardPanel(\n", - " lambda predictions, zoom_range, bin_range: weave.legacy.panels.Plot(\n", - " predictions,\n", - " x=lambda item: item[\"timestamp\"].bin(\n", - " weave.legacy.ops.timestamp_bins_nice(bin_range, 100))['start'],\n", - " y=lambda preds: preds[\"latency\"].avg(),\n", - " groupby_dims=['x'],\n", - " domain_x=zoom_range,\n", - " mark=\"line\",\n", - " ),\n", - " layout=weave.legacy.panels.BoardPanelLayout(x=16, y=6, w=8, h=6)\n", - " ),\n", - " weave.legacy.panels.BoardPanel(\n", - " lambda predictions: weave.legacy.panels.Plot(\n", - " predictions,\n", - " x=lambda item: item[\"api_cost\"].bin(\n", - " weave.legacy.ops.numbers_bins_equal(predictions['api_cost'], 50)),\n", - " y=lambda preds: preds.count(),\n", - " groupby_dims=['x'],\n", - " mark=\"bar\",\n", - " ),\n", - " layout=weave.legacy.panels.BoardPanelLayout(x=0, y=12, w=8, h=6)\n", - " ),\n", - " weave.legacy.panels.BoardPanel(\n", - " lambda predictions: weave.legacy.panels.Plot(\n", - " predictions,\n", - " x=lambda item: (item['prompt_tokens'] + item['completion_tokens']).bin(\n", - " weave.legacy.ops.number_bins_fixed(25)),\n", - " y=lambda preds: preds.count(),\n", - " groupby_dims=['x'],\n", - " mark=\"bar\",\n", - " ),\n", - " layout=weave.legacy.panels.BoardPanelLayout(x=8, y=12, w=8, h=6)\n", - " ),\n", - " weave.legacy.panels.BoardPanel(\n", - " lambda predictions: weave.legacy.panels.Plot(\n", - " predictions,\n", - " x=lambda item: item['latency'].bin(\n", - " weave.legacy.ops.numbers_bins_equal(predictions['latency'], 50)),\n", - " y=lambda preds: preds.count(),\n", - " groupby_dims=['x'],\n", - " mark=\"bar\",\n", - " ),\n", - " layout=weave.legacy.panels.BoardPanelLayout(x=16, y=12, w=8, h=6)\n", - " ),\n", - " weave.legacy.panels.BoardPanel(\n", - " lambda predictions: predictions,\n", - " layout=weave.legacy.panels.BoardPanelLayout(x=0, y=18, w=24, h=12)\n", - " )\n", - " ]\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "1d8bafc9", - "metadata": {}, - "outputs": [], - "source": [ - "board" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/experimental/Mutations.ipynb b/examples/experimental/Mutations.ipynb deleted file mode 100644 index cc49a537286..00000000000 --- a/examples/experimental/Mutations.ipynb +++ /dev/null @@ -1,70 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "dd93a251", - "metadata": {}, - "outputs": [], - "source": [ - "import weave" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ddcbd7da", - "metadata": {}, - "outputs": [], - "source": [ - "# Only run this cell the first time\n", - "table_data = weave.save([{'a': 5, 'b': 6, 'c': 'hello'}, {'a': 7, 'b': 9, 'c': 'goodbye'}], name='my-table:latest')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "0063dcd0", - "metadata": {}, - "outputs": [], - "source": [ - "dataset = weave.legacy.ops.get(\"local-artifact:///my-table:latest/obj\")\n", - "dataset" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b310adec", - "metadata": {}, - "outputs": [], - "source": [ - "# You can mutate data in the UI.\n", - "# In the panel above, switch the c column Panel to StringEditor,\n", - "# then edit one of the cells.\n", - "# To see the changes were saved, rerun cell 3." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "cf92cd4e", - "metadata": {}, - "outputs": [], - "source": [ - "# Or you can mutate data from Python. You can press the \"play\" icon in the UI to make it live update.\n", - "for i in range(10):\n", - " dataset.append({'a': 0, 'b': i, 'c': 'k'})" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/experimental/ProductionMonitoring/MonitorPanelPlotGeneric.ipynb b/examples/experimental/ProductionMonitoring/MonitorPanelPlotGeneric.ipynb deleted file mode 100644 index ca7752dbebf..00000000000 --- a/examples/experimental/ProductionMonitoring/MonitorPanelPlotGeneric.ipynb +++ /dev/null @@ -1,400 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "f7512dfa", - "metadata": {}, - "source": [ - "# Model monitoring dashboard example\n", - "\n", - "This notebook currently requires the plotly library to be installed.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "80506dea", - "metadata": {}, - "outputs": [], - "source": [ - "import weave\n", - "from weave.legacy.scripts import syndata_mon" - ] - }, - { - "cell_type": "markdown", - "id": "8519889f", - "metadata": {}, - "source": [ - "## Create synthetic data\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "7eba21e0", - "metadata": {}, - "outputs": [], - "source": [ - "#preds = syndata_mon.random_predictions(10)\n", - "\n", - "#predictions = weave.save(preds, 'predictions')\n", - "#len(preds.column('prompt').to_pylist_raw())" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8934efcf", - "metadata": {}, - "outputs": [], - "source": [ - "import logging\n", - "import json\n", - "import os\n", - "from pathlib import Path\n", - "\n", - "import tiktoken\n", - "import faiss\n", - "import numpy as np\n", - "from tenacity import (\n", - " before_sleep_log,\n", - " retry,\n", - " retry_if_exception_type,\n", - " stop_after_attempt,\n", - " wait_exponential,\n", - ")\n", - "import openai\n", - "\n", - "logger = logging.getLogger(__name__)\n", - "openai.api_key = os.environ['OPENAI_API_KEY']\n", - "\n", - "# OpenAI API functions\n", - "retry_openai_decorator = retry(\n", - " reraise=True,\n", - " stop=stop_after_attempt(4),\n", - " wait=wait_exponential(multiplier=1, min=4, max=10),\n", - " retry=(\n", - " retry_if_exception_type(openai.error.Timeout)\n", - " | retry_if_exception_type(openai.error.APIError)\n", - " | retry_if_exception_type(openai.error.APIConnectionError)\n", - " | retry_if_exception_type(openai.error.RateLimitError)\n", - " | retry_if_exception_type(openai.error.ServiceUnavailableError)\n", - " ),\n", - " before_sleep=before_sleep_log(logger, logging.WARNING),\n", - " )\n", - "\n", - "@retry_openai_decorator\n", - "def openai_embed(model, input):\n", - " return openai.Embedding.create(input = input, model=model)\n", - "\n", - "@retry_openai_decorator\n", - "def openai_chatcompletion(model, messages):\n", - " return openai.ChatCompletion.create(\n", - " model=\"gpt-3.5-turbo\", # The deployment name you chose when you deployed the ChatGPT or GPT-4 model.\n", - " messages = messages\n", - " )\n", - "\n", - "# Helper to efficiently embed a set of documents using the OpenAI embedding API\n", - "# This is from langchain\n", - "\n", - "embedding_ctx_length = 8191\n", - "OPENAI_EMBEDDING_MODEL = \"text-embedding-ada-002\"\n", - "chunk_size = 1000\n", - "\n", - "from typing import List\n", - "\n", - "def embed_texts(texts: List[str], embedding_model: str) -> List[List[float]]:\n", - " embeddings: List[List[float]] = [[] for _ in range(len(texts))]\n", - " tokens = []\n", - " indices = []\n", - " encoding = tiktoken.model.encoding_for_model(embedding_model)\n", - " for i, text in enumerate(texts):\n", - " if embedding_model.endswith(\"001\"):\n", - " # See: https://github.com/openai/openai-python/issues/418#issuecomment-1525939500\n", - " # replace newlines, which can negatively affect performance.\n", - " text = text.replace(\"\\n\", \" \")\n", - " token = encoding.encode(\n", - " text,\n", - " disallowed_special=\"all\",\n", - " )\n", - " for j in range(0, len(token), embedding_ctx_length):\n", - " tokens += [token[j : j + embedding_ctx_length]]\n", - " indices += [i]\n", - "\n", - " batched_embeddings = []\n", - " _chunk_size = chunk_size\n", - " for i in range(0, len(tokens), _chunk_size):\n", - " response = openai_embed(\n", - " embedding_model,\n", - " input=tokens[i : i + _chunk_size],\n", - " )\n", - " batched_embeddings += [r[\"embedding\"] for r in response[\"data\"]]\n", - "\n", - " results: List[List[List[float]]] = [[] for _ in range(len(texts))]\n", - " num_tokens_in_batch: List[List[int]] = [[] for _ in range(len(texts))]\n", - " for i in range(len(indices)):\n", - " results[indices[i]].append(batched_embeddings[i])\n", - " num_tokens_in_batch[indices[i]].append(len(tokens[i]))\n", - "\n", - " for i in range(len(texts)):\n", - " _result = results[i]\n", - " if len(_result) == 0:\n", - " average = embed_with_retry(\n", - " embedding_model,\n", - " input=\"\",\n", - " )[\"data\"][0][\"embedding\"]\n", - " else:\n", - " average = np.average(\n", - " _result, axis=0, weights=num_tokens_in_batch[i]\n", - " )\n", - " embeddings[i] = (average / np.linalg.norm(average)).tolist()\n", - "\n", - " return embeddings" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "6ab2debd", - "metadata": {}, - "outputs": [], - "source": [ - "import pandas\n", - "data = pandas.read_csv('/Users/shawn/datasets/wandb_export_2023-06-03T15_01_20.066-07_00.csv')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "4c084173", - "metadata": {}, - "outputs": [], - "source": [ - "data" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "eef1fb56", - "metadata": {}, - "outputs": [], - "source": [ - "embeddings = embed_texts(data['question'][:100], OPENAI_EMBEDDING_MODEL)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "45c9bcf8", - "metadata": {}, - "outputs": [], - "source": [ - "from weave.legacy.ecosystem import umap" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "54a1f09d", - "metadata": {}, - "outputs": [], - "source": [ - "umap.umap_projection(embeddings, {})" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "0781ddcd", - "metadata": {}, - "outputs": [], - "source": [ - "len(embeddings)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c190e190", - "metadata": {}, - "outputs": [], - "source": [ - "from sklearn.datasets import fetch_openml\n", - "from sklearn.decomposition import PCA\n", - "import numpy as np\n", - "import matplotlib.pyplot as plt\n", - "%matplotlib inline\n", - "\n", - "# Dimension reduction and clustering libraries\n", - "import umap\n", - "import hdbscan\n", - "import sklearn.cluster as cluster\n", - "from sklearn.metrics import adjusted_rand_score, adjusted_mutual_info_score" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "16e52ec2", - "metadata": {}, - "outputs": [], - "source": [ - "standard_embedding = umap.UMAP(random_state=42).fit_transform(embeddings)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c28defdb", - "metadata": {}, - "outputs": [], - "source": [ - "plt.scatter(standard_embedding[:, 0], standard_embedding[:, 1], s=0.1, cmap='Spectral');" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5e59fd77", - "metadata": {}, - "outputs": [], - "source": [ - "clusterable_embedding = umap.UMAP(\n", - " n_neighbors=30,\n", - " min_dist=0.0,\n", - " n_components=2,\n", - " random_state=42,\n", - ").fit_transform(embeddings)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d571528d", - "metadata": {}, - "outputs": [], - "source": [ - "clusterable_embedding.shape" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c568b42a", - "metadata": {}, - "outputs": [], - "source": [ - "plt.scatter(clusterable_embedding[:, 0], clusterable_embedding[:, 1], s=0.1, cmap='Spectral');" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "bf0920f0", - "metadata": {}, - "outputs": [], - "source": [ - "labels = hdbscan.HDBSCAN(\n", - " min_samples=10,\n", - " min_cluster_size=50,\n", - ").fit_predict(clusterable_embedding)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ead20089", - "metadata": {}, - "outputs": [], - "source": [ - "#labels\n", - "np.unique(labels, return_counts=True)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "32d4fabe", - "metadata": {}, - "outputs": [], - "source": [ - "clustered = (labels >= 0)\n", - "plt.scatter(standard_embedding[~clustered, 0],\n", - " standard_embedding[~clustered, 1],\n", - " color=(0.5, 0.5, 0.5),\n", - " s=0.1,\n", - " alpha=0.5)\n", - "plt.scatter(standard_embedding[clustered, 0],\n", - " standard_embedding[clustered, 1],\n", - " c=labels[clustered],\n", - " s=0.1,\n", - " cmap='Spectral');" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "289f1c42", - "metadata": {}, - "outputs": [], - "source": [ - "data['embedding_x'] = standard_embedding[:,0]\n", - "data['embedding_y'] = standard_embedding[:,1]\n", - "data['cluster_id'] = labels.astype(str)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "aafaa6f9", - "metadata": {}, - "outputs": [], - "source": [ - "weave.show(data)" - ] - }, - { - "cell_type": "markdown", - "id": "cdd59e39", - "metadata": {}, - "source": [ - "## Create a Weave Board for the data\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "22be0bd4", - "metadata": {}, - "outputs": [], - "source": [ - "from weave.legacy.panels_py import panel_autoboard\n", - "\n", - "panel_autoboard.auto_panels(predictions)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/experimental/ProductionMonitoring/ProductionMonitoringConceptualOverview.ipynb b/examples/experimental/ProductionMonitoring/ProductionMonitoringConceptualOverview.ipynb deleted file mode 100644 index 84fd549f7c7..00000000000 --- a/examples/experimental/ProductionMonitoring/ProductionMonitoringConceptualOverview.ipynb +++ /dev/null @@ -1,358 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "36a9f92a", - "metadata": {}, - "source": [ - "# W&B Production Monitoring Overview\n", - "\n", - "This notebook demonstrates how to monitor production models with W&B through an illustrative example. We will train a model to correctly identify handwritten digits, then monitor a locally deployed version of the model. We create a gradio app which runs in the notebook and lets a user draw/\"handwrite\" characters with the mouse and give live feedback by labeling the character as the digit 0-9.\n", - "\n", - "_Note: To keep the example focused on important code, much of the dataset manipulation, modelling, and other utilities are packaged in local files and imported here_\n", - "\n", - "# Step 0: Setup & import dependencies" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3af52605", - "metadata": {}, - "outputs": [], - "source": [ - "!pip install tensorflow\n", - "!pip install gradio\n", - "!pip install weave" - ] - }, - { - "cell_type": "markdown", - "id": "55afcd6c", - "metadata": {}, - "source": [ - "Log in to W&B to sync these examples to your W&B account, where you can view, interact with, and customize the resulting Tables and Boards." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c9e3e332", - "metadata": {}, - "outputs": [], - "source": [ - "import wandb\n", - "wandb.login()" - ] - }, - { - "cell_type": "markdown", - "id": "48354a22", - "metadata": {}, - "source": [ - "Set your W&B entity (username or team name) and optionally rename the destination project." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "424dc619", - "metadata": {}, - "outputs": [], - "source": [ - "WB_ENTITY = \"shawn\"\n", - "WB_PROJECT = \"prodmon_mnist\"" - ] - }, - { - "cell_type": "markdown", - "id": "8daaf18d", - "metadata": {}, - "source": [ - "# Step 1: Get data\n", - "In this example, we will use `keras.datasets.mnist.load_data()` to load in the MNIST dataset. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "de553636", - "metadata": {}, - "outputs": [], - "source": [ - "import model_util\n", - "\n", - "dataset = model_util.get_dataset()\n", - "model_util.image_from_array(dataset[0][0])" - ] - }, - { - "cell_type": "markdown", - "id": "6822a544", - "metadata": {}, - "source": [ - "# Step 2: Train model\n", - "Next we will train a classic NN to predict the digits" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "39bf1bc2", - "metadata": {}, - "outputs": [], - "source": [ - "model = model_util.train_model(*dataset, conv_layers=0, epochs=1) # 1 epoch so we can actually see some errors" - ] - }, - { - "cell_type": "markdown", - "id": "68a38ddd", - "metadata": {}, - "source": [ - "# Step 3: Query model\n", - "Now, let's query the model! Normally there is a little pre- and post- processing needed to make a prediction - we will write a short function to handle this for us." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5b8afe99", - "metadata": {}, - "outputs": [], - "source": [ - "import numpy as np\n", - "import json\n", - "\n", - "def simple_predict(image_arr):\n", - " # Prepare image for model\n", - " tensor = (image_arr.astype(\"float32\")).reshape(1, 28, 28, 1)\n", - "\n", - " # Make the prediction\n", - " prediction = model.predict(tensor, verbose=False)\n", - "\n", - " # In this application, we need to reshape the output:\n", - " raw_predictions = prediction[0].tolist()\n", - " logits = {\n", - " str(k): v for k, v in zip(range(10), raw_predictions)\n", - " }\n", - " \n", - " prediction = np.argmax(raw_predictions).tolist()\n", - " \n", - " return {\"logits\": logits, \"prediction\": prediction}\n", - "\n", - "_, _, x_test, y_test = dataset\n", - "for i in range(10):\n", - " image_arr = x_test[i]\n", - " truth = y_test[i]\n", - " preds = simple_predict(image_arr)\n", - " \n", - " print(f\"Input: {truth}\")\n", - " display(model_util.image_from_array(image_arr))\n", - " print(f\"Prediction: {preds['prediction']}\")\n", - " print(f\"Logits: {json.dumps(preds['logits'], indent=2)}\")\n", - " print(\"\")\n", - " " - ] - }, - { - "cell_type": "markdown", - "id": "8826acf8", - "metadata": {}, - "source": [ - "# Step 3A: Save predictions with W&B Weave using StreamTable\n", - "With W&B's Weave library, we can stream any data to W&B for storage and further analysis." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9c59bccb", - "metadata": {}, - "outputs": [], - "source": [ - "import weave\n", - "weave.use_frontend_devmode()\n", - "from weave.legacy.monitoring import StreamTable\n", - "\n", - "# Initialize a stream table\n", - "# (optionally change the name argument to any string\n", - "# that follows the wandbentity_name/project_name/table_name format)\n", - "st = StreamTable(f\"{WB_ENTITY}/{WB_PROJECT}/logged_predictions\")\n", - "_, _, x_test, y_test = dataset\n", - "for i in range(100):\n", - " image_arr = x_test[i]\n", - " truth = y_test[i].tolist()\n", - " preds = simple_predict(image_arr)\n", - " \n", - " # Log the data\n", - " st.log({\n", - " **preds,\n", - " \"image\": model_util.image_from_array(image_arr),\n", - " \"truth\": truth\n", - " })\n", - "\n", - "# Optional: wait for the logs to finish uploading (nicer for live demos)\n", - "st.finish()\n", - "\n", - "# Show the StreamTable\n", - "st " - ] - }, - { - "cell_type": "markdown", - "id": "dcb0cc29", - "metadata": {}, - "source": [ - "# Step 3B: Save predictions with W&B Weave using `monitor` decorator\n", - "This pattern of logging inputs and outputs of a functions is so common, that we provide a decorator which automatically logs a function's I/O." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "90ab5fe4", - "metadata": {}, - "outputs": [], - "source": [ - "from weave.legacy.monitoring import monitor\n", - "import numpy as np\n", - "\n", - "mon = monitor.init_monitor(f\"{WB_ENTITY}/{WB_PROJECT}/monitor_predict_function\")\n", - "\n", - "def preprocess(span):\n", - " span.inputs['image'] = model_util.image_from_array(span.inputs['image_arr'])\n", - " del span.inputs['image_arr']\n", - "\n", - "@mon.trace(\n", - " # An preprocessor allows the function arguments to be pre-processed before logging.\n", - " preprocess = preprocess\n", - ")\n", - "def monitor_predict(image_arr):\n", - " # Prepare image for model\n", - " tensor = (image_arr.astype(\"float32\")).reshape(1, 28, 28, 1)\n", - "\n", - " # Make the prediction\n", - " prediction = model.predict(tensor, verbose=False)\n", - "\n", - " # In this application, we need to reshape the output:\n", - " raw_predictions = prediction[0].tolist()\n", - " logits = {\n", - " str(k): v for k, v in zip(range(10), raw_predictions)\n", - " }\n", - " \n", - " prediction = np.argmax(raw_predictions).tolist()\n", - " \n", - " return {\"logits\": logits, \"prediction\": prediction}\n", - "\n", - "_, _, x_test, y_test = dataset\n", - "for i in range(100):\n", - " image_arr = x_test[i]\n", - " truth = y_test[i].tolist()\n", - " # Use the added monitor_attributes argument to add additional data\n", - " preds = monitor_predict(image_arr, monitor_attributes={'truth': truth})" - ] - }, - { - "cell_type": "markdown", - "id": "d3896993", - "metadata": {}, - "source": [ - "# Step 4: End-to-end example\n", - "Typically a production application will contain a prediction service that provides predictions to a client. To demonstrate this in a notebook, we will create a `PredictionService` and an `AppUI`: a small interface which lets the user to draw an image, view the prediction, and give feedback on a result (in this case, correctly label a handdrawn digit 0-9). These communicate via `predict` and `record_feedback` methods. \n", - "\n", - "Note: this is purely for example purposes—your production systems may widely vary in structure_" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "efc97d6a", - "metadata": {}, - "outputs": [], - "source": [ - "# TODO: not yet working with new API\n", - "\n", - "# import app_util\n", - "# from weave.legacy.monitoring import monitor\n", - "# import PIL\n", - "# import numpy as np\n", - "\n", - "# class PredictionService(app_util.PredictionServiceInterface):\n", - "# def __init__(self, model):\n", - "# self.model = model\n", - "# self.last_prediction = {}\n", - " \n", - "# @monitor(auto_log = False, entity_name=WB_ENTITY, project_name=WB_PROJECT)\n", - "# def _raw_predict(self, pil_image: PIL.Image) -> dict:\n", - "# # Prepare image for model\n", - "# tensor = (np.array(pil_image.resize((28, 28))).astype(\"float32\") / 255).reshape(1, 28, 28, 1)\n", - "\n", - "# # Make the prediction\n", - "# prediction = self.model.predict(tensor, verbose=False)\n", - "\n", - "# # In this application, we need to reshape the output:\n", - "# raw_predictions = prediction[0].tolist()\n", - "# logits = {\n", - "# str(k): v for k, v in zip(range(10), raw_predictions)\n", - "# }\n", - "\n", - "# prediction = np.argmax(raw_predictions).tolist()\n", - "\n", - "# return {\"logits\": logits, \"prediction\": prediction}\n", - " \n", - "# def _update_last_prediction(self, prediction) -> None:\n", - "# if len(self.last_prediction) > 0:\n", - "# last_pred = self.last_prediction.pop(list(self.last_prediction.keys())[0])\n", - "# last_pred.finalize()\n", - "# self.last_prediction[prediction.id] = prediction\n", - "\n", - " \n", - "# def predict(self, pil_image: PIL.Image) -> app_util.Prediction:\n", - "# record = self._raw_predict(pil_image)\n", - " \n", - "# # Cache the last prediction for ground_truth recording\n", - "# self._update_last_prediction(record)\n", - " \n", - "# # Return the prediction\n", - "# return app_util.Prediction(record.get()['logits'], record.id)\n", - " \n", - "# def record_feedback(self, prediction_id: str, feedback: int) -> None:\n", - "# if prediction_id not in self.last_prediction:\n", - "# return\n", - "\n", - "# # Get the past prediction\n", - "# prediction = self.last_prediction.pop(prediction_id)\n", - " \n", - "# # Save the user feedback\n", - "# prediction.add_data({'user_feedback': feedback})\n", - " \n", - "# # Log the results\n", - "# prediction.finalize()\n", - " \n", - "# app_util.render_app(PredictionService(model))" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/experimental/ProductionMonitoring/stream_table_api.ipynb b/examples/experimental/ProductionMonitoring/stream_table_api.ipynb deleted file mode 100644 index ec47a481774..00000000000 --- a/examples/experimental/ProductionMonitoring/stream_table_api.ipynb +++ /dev/null @@ -1,160 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "eccb44d3", - "metadata": {}, - "source": [ - "# Weave StreamTable Usage\n", - "\n", - "This notebook demonstrates basic Weave StreamTable usage with interactive examples.\n", - "\n", - "## Step 0: Setup\n", - "\n", - "All the StreamTables created in this notebook will be saved to the WB_PROJECT under the WB_ENTITY account on the public W&B cloud. \n", - "\n", - "**Please login to W&B and set your WB_ENTITY** before running this demo. If entity is not provided explicitly, this will attempt to default to the current logged-in entity if one is available." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3e034bf1", - "metadata": {}, - "outputs": [], - "source": [ - "!pip install weave\n", - "import wandb\n", - "wandb.login()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "51c30e7a", - "metadata": {}, - "outputs": [], - "source": [ - "import weave\n", - "from weave.legacy.monitoring import StreamTable" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5684f0b8", - "metadata": {}, - "outputs": [], - "source": [ - "STREAM_TABLE_NAME = \"my_stream_table\"\n", - "WB_PROJECT = \"mesa\"\n", - "WB_ENTITY = \"\"" - ] - }, - { - "cell_type": "markdown", - "id": "6f676d96", - "metadata": {}, - "source": [ - "## Step 1: Define a StreamTable\n", - "\n", - "StreamTable has a single required argument: the name of the StreamTable object.\n", - "\n", - "```python\n", - "st = StreamTable(\"stacey/mesa/my_stream_table\")\n", - "```\n", - "\n", - "This takes the form `my_wb_entity/my_wb_project_name/my_stream_table_name` where you can modify the component names to the relevant strings (e.g. your W&B username or shared W&B team name, a new or existing W&B project name).\n", - "\n", - "## Step 2: Log some data\n", - "\n", - "To add rows to the StreamTable, call `.log()` on the StreamTable object. \n", - "`.log()` accepts a single dictionary or a list of dictionaries, where each dictionary entry corresponds to one row of the table. In each dictionary, the keys are column names and the values are the corresponding cell values.\n", - "\n", - "```python\n", - "st.log({\"one_column_name\" : \"value_a\", \"another_column_name\" : 7})\n", - "st.log([{\"one_column_name\" : \"value_b\", \"another_column_name\" : 19},\n", - " {\"one_column_name\" : \"value_c\", \"another_column_name\" : 28},\n", - " {\"one_column_name\" : \"value_d\", \"another_column_name\" : 36}]\n", - "```\n", - "\n", - "The first call to `.log()` will return a Weave Panel URL, where you can view, edit, and save the resulting StreamTable as a Weave Board, of the form:\n", - "\n", - "View data at: https://weave.wandb.ai/?exp=get%28%0A++++%22wandb-artifact%3A%2F%2F%2Fstacey%2Fmesa%2Fmy_stream_table%3Alatest%2Fobj%22%29%0A++.rows" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b928c2e0", - "metadata": {}, - "outputs": [], - "source": [ - "st = StreamTable(f\"{WB_ENTITY}/{WB_PROJECT}/{STREAM_TABLE_NAME}\")\n", - "\n", - "# log data to the StreamTable as a dictionary or list of dictionaries\n", - "st.log({\"col_a\" : \"1\", \"col_b\" : \"17\", \"col_c\" : \"42\"})\n", - "\n", - "# show the StreamTable\n", - "st" - ] - }, - { - "cell_type": "markdown", - "id": "83cec916", - "metadata": {}, - "source": [ - "All log calls on a given StreamTable instance will append the given rows to that instance.\n", - "\n", - "In a notebook, the StreamTable variable on a line by itself will return a Weave Panel view of the StreamTable. The StreamTable will contain all the logged columns and their values, as well as a `timestamp` column indicating when the row was logged. By default, rows will be ordered by oldest first. You can modify a StreamTable Panel from the UI to sort by columns, group by column values, filter for specific ranges or values, etc.\n", - "\n", - "**Note:** If you would like to customize and save a specific view of a StreamTable Panel, open the StreamTable Panel in a new window as a Board and edit/save a Board from this seed panel. There are two options to achieve this:\n", - "* via the weave.wandb.ai/?exp=... URL\n", - "* via \"Open in new tab\" arrow button, revealed in the menu when you hover on the right side of a StreamTable panel displayed in the notebok)\n", - "\n", - "## Step 3: Log more data & explore the results!\n", - "\n", - "Continue logging as much data as you like to any StreamTable instance. You can keep a reference to a given Python StreamTable object in your notebook session or script, and you can reconnect to the same StreamTable instance across multiple sessions/runs of your script via the StreamTable's unique name (e.g. `st = StreamTable(\"stacey/mesa/my_stream_table\")` ) and keep adding rows. Multiple/parallel processes writing to the same StreamTable are also supported—the server will use a queue to order any concurrent messages.\n", - "\n", - "If you save the StreamTable Panel as a Board, the Board will continue to update as you send more data to the same StreamTable instance." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "294d68c0", - "metadata": {}, - "outputs": [], - "source": [ - "st.log({\"col_a\" : 5, \"col_b\" : -24, \"col_c\" : \"hello\"})\n", - "st.log([{\"col_a\" : 255, \"col_b\" : 3.1415926, \"col_c\" : \"hi!\"}])\n", - "\n", - "# optional: wait for all the rows to finish logging before loading\n", - "st.finish()\n", - "\n", - "st" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/experimental/Table Summary Panel.ipynb b/examples/experimental/Table Summary Panel.ipynb deleted file mode 100644 index 11f63f7ba68..00000000000 --- a/examples/experimental/Table Summary Panel.ipynb +++ /dev/null @@ -1,95 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "e9f32c0f", - "metadata": {}, - "outputs": [], - "source": [ - "import weave\n", - "# Weave package now defaults to eager mode, but lazy mode required for this example notebook for now.\n", - "weave.use_lazy_execution()\n", - "import xgboost\n", - "from weave.legacy.ecosystem import sklearn" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "50ff1335", - "metadata": {}, - "outputs": [], - "source": [ - "dataset = weave.use(sklearn.ca_housing_dataset(1))\n", - "dataset_arrow = weave.legacy.ops.dataframe_to_arrow(dataset)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "1f9dcd73", - "metadata": {}, - "outputs": [], - "source": [ - "import typing\n", - "\n", - "@weave.op()\n", - "def table_summary(table: typing.Any) -> list[weave.legacy.panels.Panel]:\n", - " if not table:\n", - " # TODO: type\n", - " return\n", - " col_names = list(table[0].keys())\n", - " with weave.legacy.context_state.lazy_execution():\n", - " cols = {col_name: weave.legacy.ops.legacy.ops_arrow.dict.pick(table, col_name) for col_name in col_names}\n", - " panels = []\n", - " for col_name, col_values in cols.items():\n", - " panels.append(weave.legacy.panels.LabeledItem(item=col_values, label=col_name))\n", - " return panels" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5d464ef4", - "metadata": {}, - "outputs": [], - "source": [ - "data = weave.save(dataset_arrow, 'my-data')\n", - "table_summary(dataset_arrow)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "47c4cdd9", - "metadata": {}, - "outputs": [], - "source": [ - "# Currently broken\n", - "#weave.publish(table_summary)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.8" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/experimental/app/Embeddings.ipynb b/examples/experimental/app/Embeddings.ipynb deleted file mode 100644 index b777bf90fc8..00000000000 --- a/examples/experimental/app/Embeddings.ipynb +++ /dev/null @@ -1,123 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "d3f10990", - "metadata": {}, - "source": [ - "## Embedding explorer\n", - "\n", - "- Embeds a string column using OpenAI Embeddings\n", - "- Follows the clustering / projection approach from here: https://umap-learn.readthedocs.io/en/latest/clustering.html" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "13a3497f", - "metadata": {}, - "outputs": [], - "source": [ - "import pandas\n", - "import weave\n", - "from weave.legacy.ecosystem import openai\n", - "from weave.legacy.ecosystem import umap\n", - "from weave.legacy.ecosystem import hdbscan" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "e68f6c86", - "metadata": {}, - "outputs": [], - "source": [ - "#raw_data = pandas.read_csv('/Users/shawn/datasets/wandb_export_2023-06-03T15_01_20.066-07_00.csv')\n", - "raw_data = pandas.read_csv('wandb_export_2023-07-25T11_43_18.362-04_00.csv')\n", - "\n", - "#raw_data['request_timestamp'] = pandas.to_datetime(raw_data['request_timestamp'], unit='ms', utc=True)\n", - "\n", - "data = weave.save(weave.legacy.ops.dataframe_to_arrow(raw_data), 'data')\n", - "# from weave.legacy.panels_py import panel_autoboard\n", - "# panel_autoboard.auto_panels(data)\n", - "#data" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "500e19b1", - "metadata": {}, - "outputs": [], - "source": [ - "data" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "e194b3ce", - "metadata": {}, - "outputs": [], - "source": [ - "weave.legacy.panels.Board(\n", - " vars={\n", - " 'data': data,\n", - " 'limit': 1005,\n", - " 'limited_data': lambda data, limit: data.limit(limit),\n", - " 'embeddings': lambda limited_data: openai.openai_embed(limited_data['question'], {\"model\": \"text-embedding-ada-002\"}),\n", - " 'clusterable_projection': lambda embeddings: umap.umap_project(\n", - " embeddings, {\n", - " 'n_neighbors': 30,\n", - " 'min_dist': 0,\n", - " 'n_components': 2,\n", - " }\n", - " ),\n", - " 'clusters': lambda clusterable_projection: hdbscan.hdbscan_cluster(clusterable_projection, {\n", - " 'min_samples': 10,\n", - " 'min_cluster_size': 50\n", - " }),\n", - " 'projection': lambda embeddings: umap.umap_project(embeddings, {})\n", - " },\n", - " panels=[\n", - " weave.legacy.panels.BoardPanel(\n", - " lambda limited_data, projection, clusters: weave.legacy.panels.Plot(\n", - " limited_data,\n", - " x=lambda row, index: projection[index][0],\n", - " y=lambda row, index: projection[index][1],\n", - " label=lambda row, index: clusters[index],\n", - " ),\n", - " id='projection_plot',\n", - " layout=weave.legacy.panels.BoardPanelLayout(x=0, y=0, w=24, h=12)\n", - " ),\n", - " weave.legacy.panels.BoardPanel(\n", - " lambda projection_plot: projection_plot.selected_data(),\n", - " layout=weave.legacy.panels.BoardPanelLayout(x=0, y=0, w=24, h=12)\n", - " )\n", - " ]\n", - ")" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/experimental/app/RunChain.ipynb b/examples/experimental/app/RunChain.ipynb deleted file mode 100644 index 4c4a82a6338..00000000000 --- a/examples/experimental/app/RunChain.ipynb +++ /dev/null @@ -1,177 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "d6a6e7cf", - "metadata": {}, - "source": [ - "## Example of plotting RunChains\n", - "\n", - "As described here: https://github.com/wandb/runchain" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "bd34fae3", - "metadata": {}, - "outputs": [], - "source": [ - "import weave\n", - "from weave.legacy.ecosystem import wandb" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "baa0a8ac", - "metadata": {}, - "outputs": [], - "source": [ - "# big project, maybe 41 deep\n", - "#chain = wandb.run_chain('shawn/branchtest_400_200_1000/2ji8emz2')\n", - "\n", - "# big project, 61 deep\n", - "#chain = wandb.run_chain('shawn/branchtest_400_200_1000/sf0vvene')\n", - "\n", - "# big project, 141 deep\n", - "#chain = wandb.run_chain('shawn/branchtest_400_200_1000/hja8k932')\n", - "\n", - "# 3 deep, 200 metrics, 10k steps each (the default, this is fast for CI)\n", - "chain = wandb.run_chain('shawn/run_chain_20_200_10000/h4vz7n29')\n", - "\n", - "# 14 deep, 200 metrics, 10k steps each\n", - "#chain = wandb.run_chain('shawn/run_chain_20_200_10000/6wdyzlq9')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "0431fff0", - "metadata": {}, - "outputs": [], - "source": [ - "# display the chain information\n", - "chain" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "4fd52674", - "metadata": {}, - "outputs": [], - "source": [ - "# or fetch the chain object\n", - "weave.use(chain)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ee1bbb90", - "metadata": {}, - "outputs": [], - "source": [ - "weave.use(chain.segments.count())" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "55b0b5c3", - "metadata": {}, - "outputs": [], - "source": [ - "# view the chain's metrics table\n", - "chain.history()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "67caf99a", - "metadata": {}, - "outputs": [], - "source": [ - "# Display plots from a RunChain. You need to do some resizing to seem them all currently.\n", - "\n", - "def plot_metric(run, metric_name):\n", - " return weave.legacy.panels.Plot(\n", - " run.history(),\n", - " series=[\n", - " # Use two plot series, one for the avg line, and one for\n", - " # min/max area. We could reduce duplication here a bit more\n", - " # by updating the Plot API to allow common features of series\n", - " # to be factored out.\n", - " weave.legacy.panels.Series(\n", - " run.history(),\n", - " select_functions={\n", - " 'x': lambda row, index: index.bin(\n", - " weave.legacy.ops.numbers_bins_equal(\n", - " weave.legacy.ops.make_list(a=0, b=run.history().count()), 250))[\"start\"],\n", - " 'y': lambda row: row[metric_name].avg()\n", - " },\n", - " groupby_dims=['x'],\n", - " constants=weave.legacy.panels.PlotConstants(\n", - " mark='line'\n", - " )\n", - " ),\n", - " weave.legacy.panels.Series(\n", - " run.history(),\n", - " select_functions={\n", - " 'x': lambda row, index: index.bin(\n", - " weave.legacy.ops.numbers_bins_equal(\n", - " weave.legacy.ops.make_list(a=0, b=run.history().count()), 250))[\"start\"],\n", - " 'y': lambda row: row[metric_name].min(),\n", - " 'y2': lambda row: row[metric_name].max(),\n", - " },\n", - " groupby_dims=['x'],\n", - " constants=weave.legacy.panels.PlotConstants(\n", - " mark='area'\n", - " )\n", - " )\n", - " ],\n", - " )\n", - "\n", - "board = weave.legacy.panels.Board(\n", - " vars={\n", - " 'run_chain': chain,\n", - " },\n", - " panels=[\n", - " weave.legacy.panels.BoardPanel(\n", - " lambda run_chain: weave.legacy.panels.Group(\n", - " layoutMode='flow',\n", - " #gridConfig=bank.flow_nxn(2, 3),\n", - " items={k: plot_metric(run_chain, k) for k in ['metric' + str(i) for i in range(10)]}\n", - " ),\n", - " layout=weave.legacy.panels.BoardPanelLayout(x=0, y=0, w=24, h=12)\n", - " )\n", - " ]\n", - ")\n", - "board" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/experimental/app/beautiful_runs.ipynb b/examples/experimental/app/beautiful_runs.ipynb deleted file mode 100644 index a4d880471db..00000000000 --- a/examples/experimental/app/beautiful_runs.ipynb +++ /dev/null @@ -1,161 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "632b052f", - "metadata": {}, - "source": [ - "## Beautiful run plot rendering\n", - "\n", - "This notebook shows how to create a Weave Board with W&B workspace-like behavior.\n", - "\n", - "We group data along the x-axis into 250 bins, and exactly compute the min / max / avg of each bucket. Unlike the W&B workspace, this computation is exact because it happens on unsampled data in the Weave engine. Only the final grouped result is sent back to the UI.\n", - "\n", - "You can zoom on any of the plots by dragging a box. All plots are zoom synchronized using Weave 2-way binding, and all plots automatically rebin for the new window.\n", - "\n", - "We've also factored out a \"groupby\" variable so you can group by run parameters from the varbar. You can change it to 'id' or 'config.b' to try other groupings.\n", - "\n", - "All of this behavior is complete defined by the code below.\n", - "\n", - "_TODO: There is one major outstanding issue here: we're using the start of the bucket for the x-coordinate\n", - "instead of the midpoint! Fix this!_\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d2492048", - "metadata": {}, - "outputs": [], - "source": [ - "import weave\n", - "from weave.legacy.scripts import syndata" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "af8e89a5", - "metadata": {}, - "outputs": [], - "source": [ - "# Generate synthetic run data for now\n", - "runs = weave.save(syndata.random_runs(10, 10000, 10), 'runs')\n", - "# Doing this unnest operation in the UI doesn't work (like in the varbar). I don't remember\n", - "# if the types are wrong, or what the specific situation is.\n", - "# TODO: Figure out why we have to do this!\n", - "unnested_runs = weave.save(weave.use(runs.unnest()), 'random_runs_unnested')\n", - "#unnested_runs" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "69f0792f", - "metadata": {}, - "outputs": [], - "source": [ - "\n", - "def plot_for_metric(metric_name):\n", - " return weave.legacy.panels.BoardPanel(\n", - " lambda runs, groupby, zoom_domain_x, bin_domain_x: weave.legacy.panels.Plot(\n", - " runs,\n", - " series=[\n", - " # Use two plot series, one for the avg line, and one for\n", - " # min/max area. We could reduce duplication here a bit more\n", - " # by updating the Plot API to allow common features of series\n", - " # to be factored out.\n", - " weave.legacy.panels.Series(\n", - " runs,\n", - " select_functions={\n", - " 'x': lambda row: row[\"history.step\"].bin(\n", - " weave.legacy.ops.numbers_bins_equal(bin_domain_x, 250))[\"start\"],\n", - " 'label': lambda row: row[groupby],\n", - " 'y': lambda row: row[\"history\"][metric_name].avg()\n", - " },\n", - " groupby_dims=['x', 'label'],\n", - " constants=weave.legacy.panels.PlotConstants(\n", - " mark='line'\n", - " )\n", - " ),\n", - " weave.legacy.panels.Series(\n", - " runs,\n", - " select_functions={\n", - " 'x': lambda row: row[\"history.step\"].bin(\n", - " weave.legacy.ops.numbers_bins_equal(bin_domain_x, 250))[\"start\"],\n", - " 'label': lambda row: row[groupby],\n", - " 'y': lambda row: row[\"history\"][metric_name].min(),\n", - " 'y2': lambda row: row[\"history\"][metric_name].max(),\n", - " },\n", - " groupby_dims=['x', 'label'],\n", - " constants=weave.legacy.panels.PlotConstants(\n", - " mark='area'\n", - " )\n", - " )\n", - " ],\n", - " domain_x=zoom_domain_x,\n", - " ),\n", - " #layout=weave.legacy.panels.BoardPanelLayout(x=x, y=0, w=12, h=6)\n", - " )\n", - "\n", - "board = weave.legacy.panels.Board(\n", - " vars={\n", - " 'runs': unnested_runs,\n", - " 'groupby': 'config.a',\n", - " # This holds the zoom boundaries, and is two-way synchronized with the plot zooms\n", - " 'zoom_domain_x': None,\n", - " # This is the extent of the step field in our data (the minimum and maxium values)\n", - " 'step_domain': lambda runs: weave.legacy.ops.make_list(\n", - " a=weave.legacy.ops.numbers_min(runs['history']['step']),\n", - " b=weave.legacy.ops.numbers_max(runs['history']['step'])),\n", - " # The \"domain\" used for binning, which is the zoom range if its non-null, otherwise\n", - " # the step_extent\n", - " 'bin_domain_x': lambda zoom_domain_x, step_domain: zoom_domain_x.coalesce(step_domain)\n", - " },\n", - " # TODO: Use EachColumn to do this automatically for all metrics.\n", - " panels=[\n", - " plot_for_metric('metric0'),\n", - " plot_for_metric('metric1'),\n", - " plot_for_metric('metric2'),\n", - " plot_for_metric('metric3'),\n", - " plot_for_metric('metric4'),\n", - " plot_for_metric('metric5'),\n", - " plot_for_metric('metric6'),\n", - " plot_for_metric('metric7'),\n", - " ]\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3f157310", - "metadata": {}, - "outputs": [], - "source": [ - "board" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/experimental/app/scenario_compare.ipynb b/examples/experimental/app/scenario_compare.ipynb deleted file mode 100644 index befb14b8f0a..00000000000 --- a/examples/experimental/app/scenario_compare.ipynb +++ /dev/null @@ -1,176 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "67551526", - "metadata": {}, - "outputs": [], - "source": [ - "import random\n", - "import weave\n", - "# Weave package now defaults to eager mode, but lazy mode required for this example notebook for now.\n", - "weave.use_lazy_execution()\n", - "from weave.legacy.ecosystem import scenario" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "6e12d2d0", - "metadata": {}, - "outputs": [], - "source": [ - "# a = weave.save(5)\n", - "# b = a + 2\n", - "# weave.type_of(b)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "4882d74b", - "metadata": {}, - "outputs": [], - "source": [ - "import math\n", - "def make_run(n_scenarios, n) -> list[scenario.ScenarioResult]:\n", - " return [\n", - " scenario.ScenarioResult(\n", - " scenario_id=str(i),\n", - " metric1=random.expovariate(n / 33),\n", - " metric2=random.gauss(42, n / 100),\n", - " metric3=random.triangular(n, 11),\n", - " metric4=random.random() * random.random() * n,\n", - " metric5=random.random(),\n", - " metric6=random.betavariate(0.01, n / 2),\n", - " )\n", - " for i in range(n_scenarios)\n", - " ]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "218e28e2", - "metadata": {}, - "outputs": [], - "source": [ - "#panel_input = weave.save({'baseline': make_run(10), 'candidate': make_run(10)}, name='panel_input')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "984bcb3c", - "metadata": {}, - "outputs": [], - "source": [ - "#panel_input" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "7e1890ca", - "metadata": {}, - "outputs": [], - "source": [ - "# make some runs\n", - "\n", - "weave.save(make_run(100, 1), name='run1:latest')\n", - "weave.save(make_run(100, 2), name='run2:latest')\n", - "weave.save(make_run(100, 3), name='run3:latest')\n", - "weave.save(make_run(100, 4), name='run4:latest')\n", - "weave.save(make_run(100, 5), name='run5:latest')\n", - "pass" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9f2b3a72", - "metadata": {}, - "outputs": [], - "source": [ - "# Ugly. We need to compute the Weave type right now.\n", - "# TODO: Fix\n", - "from weave.legacy import infer_types\n", - "run_type = weave.types.List(infer_types.python_type_to_type(scenario.ScenarioResult))\n", - "\n", - "import time\n", - "runs = weave.legacy.ops.objects(run_type, 'latest', int(time.time()))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "89e4b718", - "metadata": {}, - "outputs": [], - "source": [ - "panel = weave.legacy.panels.Group(\n", - " preferHorizontal=True,\n", - " items={\n", - " \"sidebar\": weave.legacy.panels.Group(\n", - " style=\"width: 200px; padding: 16px;\",\n", - " items={\n", - " \"baseline\": weave.legacy.panels.ObjectPicker(runs, label='baseline'),\n", - " \"candidate\": weave.legacy.panels.ObjectPicker(runs, label='candidate')\n", - " }\n", - " ),\n", - " \"main\": lambda sidebar: weave.legacy.panels.Group(\n", - " items={\n", - " \"tables\": weave.legacy.panels.Group(\n", - " style=\"height: 300px;\",\n", - " preferHorizontal=True,\n", - " equalSize=True,\n", - " items={\n", - " \"baseline_table\": weave.legacy.panels.LabeledItem(\n", - " label=\"Baseline Table\",\n", - " item=weave.legacy.ops.execute(sidebar.config.items['baseline'].config.choice).get()\n", - " ),\n", - " \"candidate_table\": weave.legacy.panels.LabeledItem(\n", - " label=\"Candidate Table\",\n", - " item=weave.legacy.ops.execute(sidebar.config.items['candidate'].config.choice).get()\n", - " )\n", - " }),\n", - " \"plots\": \n", - " weave.legacy.panels.LabeledItem(\n", - " label='Scenario plots',\n", - " item=scenario.MetricsBankPanel(\n", - " weave.legacy.ops.dict_(\n", - " baseline=weave.legacy.ops.execute(sidebar.config.items['baseline'].config.choice).get(),\n", - " candidate=weave.legacy.ops.execute(sidebar.config.items['candidate'].config.choice).get()\n", - " ))\n", - " )\n", - "\n", - " }\n", - " )\n", - " })\n", - "panel" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/experimental/bert_viz.ipynb b/examples/experimental/bert_viz.ipynb deleted file mode 100644 index a971cf76964..00000000000 --- a/examples/experimental/bert_viz.ipynb +++ /dev/null @@ -1,71 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "f4fcc7fa", - "metadata": {}, - "source": [ - "# BertViz + HuggingFace: Visualize Attention for Language Models\n", - "\n", - "Integrates the amazing [bertviz package](https://github.com/jessevig/bertviz) to visualize the attention for a provided language model (here loaded in from Hugging Face).\n", - "\n", - "## Settings to explore\n", - "\n", - "* model name: select a different model from HuggingFace (or add your own :)\n", - "* input text: edit the text sample on which to visualize attention \n", - "* visualization type: try toggling the output panel type to see the full attention map across heads and layers :)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d85f7aaa", - "metadata": { - "execution": { - "iopub.execute_input": "2023-06-02T01:46:20.864046Z", - "iopub.status.busy": "2023-06-02T01:46:20.863903Z", - "iopub.status.idle": "2023-06-02T01:46:26.125190Z", - "shell.execute_reply": "2023-06-02T01:46:26.124715Z" - } - }, - "outputs": [], - "source": [ - "import weave\n", - "# Weave package now defaults to eager mode, but lazy mode required for this example notebook for now.\n", - "weave.use_lazy_execution()\n", - "from weave.legacy.ecosystem.huggingface import huggingface\n", - "\n", - "# Call a hugging face model\n", - "hf_model = huggingface().model('gpt2')\n", - "model_output = hf_model.call('The cat sat on the mat')\n", - "\n", - "# The type of .attention() is huggingface.ModelOutputType. Our bertviz panel is\n", - "# registered as the only handler for that type, so it will automatically get rendered.\n", - "\n", - "# model_output.attention()\n", - "model_output" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/experimental/composite_histograms.ipynb b/examples/experimental/composite_histograms.ipynb deleted file mode 100644 index 08cce9ad62e..00000000000 --- a/examples/experimental/composite_histograms.ipynb +++ /dev/null @@ -1,136 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "d70e6c48", - "metadata": {}, - "outputs": [], - "source": [ - "import weave\n", - "from weave.legacy.ecosystem import lens" - ] - }, - { - "cell_type": "markdown", - "id": "c3d89416", - "metadata": {}, - "source": [ - "# Composite Histograms\n", - "\n", - "Let's plot some histograms.\n", - "\n", - "## Base case: One series, no customization" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "e739d513", - "metadata": {}, - "outputs": [], - "source": [ - "lens.histogram([[1.0, 2, 3, -1.4]], 1.0, \"Composite Histogram\", ['series'])" - ] - }, - { - "cell_type": "markdown", - "id": "81fd5769", - "metadata": {}, - "source": [ - "## Two series with customization" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "301be405", - "metadata": {}, - "outputs": [], - "source": [ - "lens.histogram([[-1.8, -0.4, 0.1, -0.9, -0.77, 0.5, 0.7, 0.2, 0.1, 0.8],[0.9, 0.4, 0.5, 0.6, 1.2, 1.5, 1.6, 1.7, 2, 2.4, 2.3, 2.1]], 0.5, \"My Histogram\", [\"series A\", \"series B\"])" - ] - }, - { - "cell_type": "markdown", - "id": "ee794dd4", - "metadata": {}, - "source": [ - "## Three series" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "fba3acec", - "metadata": {}, - "outputs": [], - "source": [ - "lens.histogram([[1.0, 2, 3, -1.4],[8.1, 2.2, 3.3, 5.6, 1.1], [1.6, 1.7, 1.8, -2.0, 5]], 0.5, 'Composite histogram', ['a', 'b', 'c'])" - ] - }, - { - "cell_type": "markdown", - "id": "ac8ee3eb", - "metadata": {}, - "source": [ - "## More than three series :)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "070314da", - "metadata": {}, - "outputs": [], - "source": [ - "lens.histogram([[1.0, -20.0, -25.0, -15.0, 2.0, 3.0, 2.0, 2.0, 2.0, 2.0, 4.0, 5.0, 6.0], [1.0, 1.0, 1.0, 1.0, 1.0, 2.0, 3.0, 3.0, 3.0, 3.0, 2.0, 2.0, 2.0],[16.2, 17.9, 18.2, 2.3, -5.2, 6.0, 7.2, -10.9, 8.2,41.0], [1.3, 2.2, 5.3, 7.7, 8.8, 9.0, 2.0, 3.2, 43.7, 4.2, 5.1, 4.5, 6.9], [1.0, 2.1, 3.0, 4.1, 19.0, 6.0, 12.2, 14.9, 15.0, 16.1, 17.2]], 5.0, 'Composite histogram', ['a', 'b', 'c', 'd', 'e'])" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "2245a600", - "metadata": {}, - "outputs": [], - "source": [ - "# weave-test-skip: in-notebook ops don't yet work with server\n", - "# super simple example for now to illustrate fetching W&B data & plotting histograms\n", - "from wandb.apis import public as wandb_api\n", - "from PIL import Image\n", - "\n", - "@weave.op(render_info={\"type\": \"function\"})\n", - "def run_accuracy_barchart(runs: list[wandb_api.Run]) -> Image.Image:\n", - " runs = list(runs) #[:100] # could shorten this list\n", - " acc = [r.summary_metrics.get(\"acc\") or 0.0 for r in runs]\n", - " car_acc = [r.summary_metrics.get(\"car_acc\") or 0.0 for r in runs]\n", - " traffic_acc = [r.summary_metrics.get(\"traffic_acc\") or 0.0 for r in runs]\n", - " road_acc = [r.summary_metrics.get(\"road_acc\") or 0.0 for r in runs]\n", - " \n", - " # filter out NaNs\n", - " for l in [acc, car_acc, traffic_acc, road_acc]:\n", - " if \"NaN\" in l:\n", - " l = l.remove(\"NaN\")\n", - "\n", - " return weave.use(lens.histogram([acc, car_acc, traffic_acc, road_acc],\n", - " bin_size=0.05,\n", - " chart_title=\"Semantic Segmentation Accuracy Across Experiments by Label Class\", \n", - " series_names=[\"Overall acc\", \"Car acc\", \"Traffic acc\", \"Road acc\"]\n", - " )\n", - " )\n", - "\n", - "runs = weave.legacy.ops.project('stacey', 'deep-drive').runs()\n", - "run_accuracy_barchart(runs)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/experimental/dir_browsing.ipynb b/examples/experimental/dir_browsing.ipynb deleted file mode 100644 index eaa36563837..00000000000 --- a/examples/experimental/dir_browsing.ipynb +++ /dev/null @@ -1,26 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "79d64dea", - "metadata": {}, - "outputs": [], - "source": [ - "import os\n", - "import weave\n", - "\n", - "#weave.legacy.ops.local_path(os.path.abspath('../weave/testdata'))" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/experimental/ecosystem.ipynb b/examples/experimental/ecosystem.ipynb deleted file mode 100644 index b8f6c86266d..00000000000 --- a/examples/experimental/ecosystem.ipynb +++ /dev/null @@ -1,25 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "3d5d0d34", - "metadata": {}, - "outputs": [], - "source": [ - "import weave\n", - "from weave.legacy.ecosystem import ecosystem\n", - "ecosystem()" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/experimental/huggingface_datasets.ipynb b/examples/experimental/huggingface_datasets.ipynb deleted file mode 100644 index 76e7365cc2d..00000000000 --- a/examples/experimental/huggingface_datasets.ipynb +++ /dev/null @@ -1,89 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "897e0314", - "metadata": {}, - "source": [ - "# HuggingFace Datasets\n", - "\n", - "A quick overview of exploring [Datasets from HuggingFace](https://huggingface.co/docs/datasets/index).\n", - "\n", - "### Known issues\n", - "\n", - "- Using a dataset often reboots the weave server the first time, causing the first request to fail. Run again and it'll work\n", - "- We only load 100 items" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ba78a68c", - "metadata": {}, - "outputs": [], - "source": [ - "import weave\n", - "# Weave package now defaults to eager mode, but lazy mode required for this example notebook for now.\n", - "weave.use_lazy_execution()\n", - "from weave.legacy.ecosystem import huggingface as hf" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3e3b90c5", - "metadata": {}, - "outputs": [], - "source": [ - "hf.hf_datasets()" - ] - }, - { - "cell_type": "markdown", - "id": "d82a6ac3", - "metadata": {}, - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b861a911", - "metadata": {}, - "outputs": [], - "source": [ - "hf.dataset('app_reviews')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9935abb0", - "metadata": {}, - "outputs": [], - "source": [ - "hf.dataset('fashion_mnist')" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/experimental/huggingface_models.ipynb b/examples/experimental/huggingface_models.ipynb deleted file mode 100644 index 8cc3c98028a..00000000000 --- a/examples/experimental/huggingface_models.ipynb +++ /dev/null @@ -1,131 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "d46881ea", - "metadata": {}, - "outputs": [], - "source": [ - "import weave\n", - "# Weave package now defaults to eager mode, but lazy mode required for this example notebook for now.\n", - "weave.use_lazy_execution()\n", - "from weave.legacy.ecosystem.huggingface import huggingface as hf" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9d181896", - "metadata": {}, - "outputs": [], - "source": [ - "# Models\n", - "#hf().models()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "cb3fb536", - "metadata": {}, - "outputs": [], - "source": [ - "# Model card\n", - "text_gen_model = hf().model('gpt2')\n", - "text_gen_model" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5961a5c5", - "metadata": {}, - "outputs": [], - "source": [ - "# Calling a text generation model\n", - "text_gen_output = text_gen_model.call('This is the start')\n", - "text_gen_output" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "974e1a18", - "metadata": {}, - "outputs": [], - "source": [ - "# Bertviz for attention\n", - "text_gen_output.attention()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b9ab742c", - "metadata": {}, - "outputs": [], - "source": [ - "# Model card\n", - "classification_model = hf().model('bhadresh-savani/distilbert-base-uncased-emotion')\n", - "classification_model" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "493448cc", - "metadata": {}, - "outputs": [], - "source": [ - "classification_output = classification_model.call(\"I'm feeling just swell\")\n", - "classification_output" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "36305bb0", - "metadata": {}, - "outputs": [], - "source": [ - "classification_output.attention()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b1aa95d3", - "metadata": {}, - "outputs": [], - "source": [ - "# Text classification models can be explained with shap\n", - "\n", - "# TODO: we wan't chain shap_explain in Python right now. But we can in JS. Fix!\n", - "from weave.legacy.ecosystem import shap\n", - "shap.shap_explain(classification_output)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/experimental/image_gen_craiyon.ipynb b/examples/experimental/image_gen_craiyon.ipynb deleted file mode 100644 index a2535d4f58b..00000000000 --- a/examples/experimental/image_gen_craiyon.ipynb +++ /dev/null @@ -1,52 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "2088ebdf", - "metadata": {}, - "outputs": [], - "source": [ - "# weave-test-skip-all: The craiyon API takes at least 60s per prediction!\n", - "import weave" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b6e6882b", - "metadata": {}, - "outputs": [], - "source": [ - "from weave.legacy.ecosystem import craiyon\n", - "craiyon.generate('hello')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3322e1c5", - "metadata": {}, - "outputs": [], - "source": [ - "prompts = ['cats', 'dogs', 'catdogs']\n", - "panel = weave.legacy.panels.Table(prompts,\n", - " columns=[\n", - " lambda prompt: prompt,\n", - " lambda prompt: craiyon.generate(prompt)\n", - " ]\n", - " )\n", - "weave.show(panel)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/experimental/image_gen_ops.ipynb b/examples/experimental/image_gen_ops.ipynb deleted file mode 100644 index 673994b3ae9..00000000000 --- a/examples/experimental/image_gen_ops.ipynb +++ /dev/null @@ -1,126 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "22034460", - "metadata": {}, - "outputs": [], - "source": [ - "import weave\n", - "import io\n", - "from PIL import Image\n", - "import numpy as np\n", - "import matplotlib.pyplot as plt\n", - "import random\n", - "from weave.legacy import ops\n", - "\n", - "def plt_image():\n", - " \"\"\"Return current matplotlib figure as PIL Image\"\"\"\n", - " img_buf = io.BytesIO()\n", - " plt.savefig(img_buf, format='png')\n", - " im = Image.open(img_buf)\n", - " return im" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d03dc328", - "metadata": {}, - "outputs": [], - "source": [ - "# Declare an op that plots a sin weave using matplotlib, and returns the plot as an Image\n", - "\n", - "@weave.op(render_info={\"type\": \"function\"})\n", - "def sin_image(f: int) -> Image.Image: \n", - " x = np.arange(0, f * np.pi, 0.1)\n", - " y = np.sin(x)\n", - " plt.rcParams[\"figure.figsize\"] = (22, 7)\n", - " plt.plot(x, y)\n", - " \n", - " return plt_image()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "217d0cd2", - "metadata": {}, - "outputs": [], - "source": [ - "x = weave.save(7, name='my-number')\n", - "\n", - "im = sin_image(x + 2)\n", - "im" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d8d06148", - "metadata": {}, - "outputs": [], - "source": [ - "import json\n", - "from weave.legacy.ops_domain import wb_domain_types\n", - "from weave import gql_op_plugin\n", - "import wandb\n", - "\n", - "\n", - "@weave.op(\n", - " render_info={\"type\": \"function\"},\n", - " plugins=gql_op_plugin.wb_gql_op_plugin(\n", - " lambda inputs, inner: \"project {id name entity {id name}}\"\n", - " )\n", - ")\n", - "def run_accuracy_barchart(runs: list[wb_domain_types.Run]) -> Image.Image:\n", - " runs = list(runs)[:10]\n", - " runs = [wandb.Api().run(f'{r[\"project\"][\"entity\"][\"name\"]}/{r[\"project\"][\"name\"]}/{r[\"name\"]}') for r in runs]\n", - " names = [r.name for r in runs]\n", - " xs = range(len(runs))\n", - " ys = [r.summary_metrics.get(\"acc\") or 0 for r in runs]\n", - "\n", - " plt.rcParams[\"figure.figsize\"] = (15, 5)\n", - " plt.bar(xs, ys, align=\"center\", alpha=0.5)\n", - " plt.xticks(xs, names)\n", - " plt.ylabel(\"acc\")\n", - " plt.title(\"Run accuracies\")\n", - "\n", - " return plt_image()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a4dc62a2", - "metadata": {}, - "outputs": [], - "source": [ - "runs = weave.legacy.ops.project('shawn', 'fasion-sweep').runs()\n", - "run_accuracy_barchart(runs)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/experimental/layout_panels.ipynb b/examples/experimental/layout_panels.ipynb deleted file mode 100644 index a7536f0191f..00000000000 --- a/examples/experimental/layout_panels.ipynb +++ /dev/null @@ -1,206 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "cfebbf95", - "metadata": {}, - "outputs": [], - "source": [ - "import weave\n", - "from weave.legacy import panels" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "e427d65a", - "metadata": {}, - "outputs": [], - "source": [ - "# LabeledItem example\n", - "# panels.LabeledItem(item='item', label='label')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f9852987", - "metadata": {}, - "outputs": [], - "source": [ - "# Group example\n", - "# panels.Group(\n", - "# items=[\n", - "# panels.Group(\n", - "# prefer_horizontal=True,\n", - "# items=[\n", - "# panels.LabeledItem(item='item1', label='label1'),\n", - "# panels.LabeledItem(item='item2', label='label2'),\n", - "# panels.LabeledItem(item='item3', label='label3'),\n", - "# ]\n", - "# ),\n", - "# panels.LabeledItem(item='item4', label='label4'),\n", - "# panels.LabeledItem(item='item5', label='label5')\n", - "# ]\n", - "# )" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "6ee2951b", - "metadata": {}, - "outputs": [], - "source": [ - "# Card example\n", - "\n", - "panel = panels.Card(\n", - " title='GPT-3',\n", - " subtitle='OpenAI',\n", - " content=[\n", - " panels.CardTab(\n", - " name='Overview',\n", - " content=panels.Group(\n", - " items={\n", - " '0': panels.Group(\n", - " preferHorizontal=True,\n", - " items={\n", - " 'a': panels.LabeledItem(item='item1', label='label1'),\n", - " 'b': panels.LabeledItem(item='item2', label='label2'),\n", - " 'c': panels.LabeledItem(item='item3', label='label3'),\n", - " }\n", - " ),\n", - " '1': panels.LabeledItem(item='item4', label='label4'),\n", - " '2': panels.LabeledItem(item='item5', label='label5')\n", - " }\n", - " )\n", - " ),\n", - " panels.CardTab(\n", - " name='Limitations & Use',\n", - " content=panels.LabeledItem(item='tab2', label='tab2-label')\n", - " ),\n", - " \n", - " ]\n", - ")\n", - "panel" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "762467e8", - "metadata": {}, - "outputs": [], - "source": [ - "# Table in Card example\n", - "\n", - "# panels.Card(\n", - "# title='GPT-3',\n", - "# subtitle='OpenAI',\n", - "# content=[\n", - "# panels.CardTab(\n", - "# name='Overview',\n", - "# content=panels.Group(\n", - "# items=[\n", - "# panels.Group(\n", - "# prefer_horizontal=True,\n", - "# items=[\n", - "# panels.LabeledItem(item='item1', label='label1'),\n", - "# panels.LabeledItem(item='item2', label='label2'),\n", - "# panels.LabeledItem(item='item3', label='label3'),\n", - "# ]\n", - "# ),\n", - "# panels.LabeledItem(item='item4', label='label4'),\n", - "# panels.LabeledItem(\n", - "# item=[\n", - "# {\"a\": 5, \"b\": 6},\n", - "# {\"a\": 14, \"b\": 9},\n", - "# {\"a\": 4, \"b\": 22},\n", - "# ],\n", - "# height=400,\n", - "# label=\"Example\",\n", - "# ),\n", - "# ]\n", - "# )\n", - "# ),\n", - "# panels.CardTab(\n", - "# name='Limitations & Use',\n", - "# content=panels.LabeledItem(item='tab2', label='tab2-label')\n", - "# ),\n", - " \n", - "# ]\n", - "# )" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "81c83f19", - "metadata": {}, - "outputs": [], - "source": [ - "# If you want full control over table columns\n", - "\n", - "# table = panels.Table([\n", - "# {\"a\": 5, \"b\": 6},\n", - "# {\"a\": 14, \"b\": 9},\n", - "# {\"a\": 4, \"b\": 22},\n", - "# ])\n", - "# table.append_column(lambda row: row['a'] + 3, name=\"a name\")\n", - "# table.append_column(lambda row: row['a'] + row['b']) # Will have an automatic name\n", - "\n", - "# panels.LabeledItem(\n", - "# item=table,\n", - "# height=400,\n", - "# label=\"Example\",\n", - "# )" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "eaea1261", - "metadata": {}, - "outputs": [], - "source": [ - "# Plot in Labeled item (you can do the same in a Card)\n", - "\n", - "# plot = panels.Plot(\n", - "# input_node=[\n", - "# {\"a\": 5, \"b\": 6},\n", - "# {\"a\": 14, \"b\": 9},\n", - "# {\"a\": 4, \"b\": 22}],\n", - "# x=lambda row: row['a'],\n", - "# y=lambda row: row['b']\n", - "# )\n", - "\n", - "# panels.LabeledItem(\n", - "# item=plot,\n", - "# label=\"Example\",\n", - "# )" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/experimental/llm_monitor_helper.ipynb b/examples/experimental/llm_monitor_helper.ipynb deleted file mode 100644 index d2cfe85b752..00000000000 --- a/examples/experimental/llm_monitor_helper.ipynb +++ /dev/null @@ -1,198 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "ENTITY = 'timssweeney'\n", - "PROJECT = 'weave'\n", - "STREAM = 'custom_llm_monitoring_example'" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import uuid\n", - "import time\n", - "import typing\n", - "from datetime import datetime \n", - "\n", - "from weave.stream_data_interfaces import LLMCompletionDict, _LLMCompletionInputs, _LLMCompletionOutput, _LLMCompletionSummary, _LLMCompletionMessage, _LLMCompletionChoice\n", - "import tiktoken\n", - "\n", - "def count_chat_completion_tokens(\n", - " model_name: typing.Optional[str] = None,\n", - " prompt_input_messages: typing.List[_LLMCompletionMessage] = [],\n", - " completion_choices: typing.List[_LLMCompletionChoice] = []\n", - ") -> dict:\n", - " \n", - " summary = {}\n", - " if not model_name:\n", - " encoding = tiktoken.get_encoding(\"cl100k_base\")\n", - " else:\n", - " encoding = tiktoken.encoding_for_model(model_name)\n", - "\n", - " prompt_tokens = (encoding.encode(m[\"content\"]) for m in prompt_input_messages)\n", - " summary[\"prompt_tokens\"] = sum(len(c) for c in prompt_tokens)\n", - "\n", - " completion_tokens = (\n", - " encoding.encode(c[\"message\"][\"content\"]) for c in completion_choices\n", - " )\n", - " summary[\"completion_tokens\"] = sum(len(c) for c in completion_tokens)\n", - " summary[\"total_tokens\"] = summary[\"prompt_tokens\"] + summary[\"completion_tokens\"]\n", - " return summary\n", - "\n", - "def create_llm_record(\n", - "\n", - " # The end-format is that of `_LLMCompletionInputs`, but we can add helper processing to convert from other formats\n", - " inputs: typing.Union[_LLMCompletionInputs, typing.List[_LLMCompletionMessage], typing.List[str], str] = None,\n", - "\n", - " # the end-format is that of `_LLMCompletionOutput`, but we can add helper processing to convert from other formats\n", - " output: typing.Union[_LLMCompletionOutput, str] = None,\n", - "\n", - " # Required for cost analysis\n", - " model_name: str = \"\",\n", - "\n", - " # Optional fields\n", - " span_id: typing.Optional[str] = None,\n", - " name: typing.Optional[str] = None,\n", - " trace_id: typing.Optional[str] = None,\n", - " status_code: typing.Optional[str] = None,\n", - " start_time_s: typing.Optional[float] = None,\n", - " end_time_s: typing.Optional[float] = None,\n", - " parent_id: typing.Optional[str] = None,\n", - "\n", - " # Must contain dictionaries, lists, and primitives (ie json serializable)\n", - " attributes: typing.Optional[typing.Dict[str, typing.Any]] = None,\n", - "\n", - " summary: typing.Optional[_LLMCompletionSummary] = None,\n", - "\n", - " exception: typing.Optional[str] = None\n", - "):\n", - " span_id = span_id or str(uuid.uuid4())\n", - " name = name or \"llm_completion\"\n", - " trace_id = trace_id or span_id\n", - " status_code = status_code or \"UNSET\"\n", - " start_time_s = start_time_s or time.time()\n", - " end_time_s = end_time_s or (start_time_s + 1)\n", - " latency_s = end_time_s - start_time_s\n", - " # parent_id can be None\n", - " attributes = attributes or {}\n", - " \n", - "\n", - " # Input handling\n", - " if isinstance(inputs, dict):\n", - " # Assume correct format\n", - " inputs = inputs\n", - " elif isinstance(inputs, list):\n", - " messages = []\n", - " for item in inputs:\n", - " if isinstance(item, str):\n", - " messages.append(_LLMCompletionMessage(content=item))\n", - " elif isinstance(item, dict):\n", - " # Assume correct format\n", - " messages.append(item)\n", - " else:\n", - " raise ValueError(f\"Invalid type for item in inputs: {type(item)}\")\n", - " inputs = _LLMCompletionInputs(messages=messages)\n", - " elif isinstance(inputs, str):\n", - " inputs = _LLMCompletionInputs(messages=[_LLMCompletionMessage(content=inputs)])\n", - " else:\n", - " raise ValueError(f\"Invalid type for inputs: {type(inputs)}\")\n", - " \n", - "\n", - " # Output handling\n", - " if isinstance(output, dict):\n", - " # Assume correct format\n", - " output = output\n", - " elif isinstance(output, str):\n", - " output = _LLMCompletionOutput(model=model_name, choices=[_LLMCompletionChoice(message=_LLMCompletionMessage(content=output))])\n", - " else:\n", - " raise ValueError(f\"Invalid type for output: {type(output)}\")\n", - "\n", - " # Sort of odd, but we need at least one key for now\n", - " summary = summary or {}\n", - " summary = {\n", - " **(summary or {}),\n", - " \"latency_s\": latency_s,\n", - " **count_chat_completion_tokens(\n", - " model_name,\n", - " inputs['messages'],\n", - " output['choices'])\n", - " }\n", - " \n", - "\n", - " # exception can be None\n", - "\n", - " assert status_code in [\"SUCCESS\", \"ERROR\", \"UNSET\"]\n", - "\n", - " return LLMCompletionDict(\n", - " span_id = span_id,\n", - " name = name,\n", - " trace_id = trace_id,\n", - " status_code = status_code,\n", - " start_time_s = start_time_s,\n", - " end_time_s = end_time_s,\n", - " parent_id = parent_id,\n", - " attributes = attributes,\n", - " inputs = inputs,\n", - " output = output,\n", - " summary = summary,\n", - " exception = exception,\n", - " # Manually set timestamp - else it will be set to the time of the function call\n", - " timestamp = datetime.fromtimestamp(start_time_s)\n", - " )" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from weave.legacy.monitoring import StreamTable\n", - "st = StreamTable(f\"{ENTITY}/{PROJECT}/{STREAM}\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Base Case\n", - "record = create_llm_record(\n", - " inputs=\"hello\",\n", - " output=\"world\"\n", - ")\n", - "st.log(record)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "wandb-weave", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.8" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/examples/experimental/mnist_train.ipynb b/examples/experimental/mnist_train.ipynb deleted file mode 100644 index f6cb2821cc0..00000000000 --- a/examples/experimental/mnist_train.ipynb +++ /dev/null @@ -1,82 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "37f6d676", - "metadata": {}, - "outputs": [], - "source": [ - "import weave\n", - "# Weave package now defaults to eager mode, but lazy mode required for this example notebook for now.\n", - "weave.use_lazy_execution()\n", - "from weave.legacy.ecosystem import torchvision, torch_mnist_model_example" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b450f4df", - "metadata": {}, - "outputs": [], - "source": [ - "# TODO: Fix WeaveJS to automatically pick the more specific dataset_card Panel\n", - "mnist_dataset = torchvision.mnist(100) # Or try mnist.food101(100), but currently broken\n", - "mnist_dataset" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "664f3447", - "metadata": {}, - "outputs": [], - "source": [ - "hyperparams = {\n", - " 'fc_layer_size': 256,\n", - " 'dropout': 0.5,\n", - " 'epochs': 5,\n", - " 'learning_rate': 0.005,\n", - " 'batch_size': 128,\n", - "}\n", - "train_split = mnist_dataset['data']['train']\n", - "#train_split.pick('image')\n", - "model = torch_mnist_model_example.train(\n", - " train_split.pick('image'), train_split.pick('label'), hyperparams)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8d204664", - "metadata": {}, - "outputs": [], - "source": [ - "test_split_images = mnist_dataset['data']['test']['image']\n", - "preds = model.predict(test_split_images)\n", - "preds" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/experimental/object_version_compare.ipynb b/examples/experimental/object_version_compare.ipynb deleted file mode 100644 index c74bf916295..00000000000 --- a/examples/experimental/object_version_compare.ipynb +++ /dev/null @@ -1,116 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "e114a840", - "metadata": {}, - "outputs": [], - "source": [ - "import math\n", - "import weave\n", - "import typing" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "47ae117d", - "metadata": {}, - "outputs": [], - "source": [ - "@weave.op(render_info={'type': 'function'})\n", - "def weave_range(n: int) -> list[int]:\n", - " return list(range(n))\n", - "\n", - "class Point(typing.TypedDict):\n", - " x: float\n", - " y: float\n", - "\n", - "@weave.op()\n", - "def compute_points(xs: list[int], freq: float) -> list[Point]:\n", - " res: list[Point] = []\n", - " for x in xs:\n", - " res.append({\"x\": x, \"y\": math.sin(freq * x)})\n", - " return res" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "e6db91c9", - "metadata": {}, - "outputs": [], - "source": [ - "xs = weave_range(1000)\n", - "points = compute_points(xs, 0.16)\n", - "points = weave.save(points, 'points')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5c88c35e", - "metadata": {}, - "outputs": [], - "source": [ - "plot = weave.legacy.panels.Plot(points,\n", - " x=lambda row: row['x'],\n", - " y=lambda row: row['y'])\n", - "plot\n", - "\n", - "# NOTE! There is a UI bug here. You need to click the gear icon and\n", - "# then \"Reset & Automate Plot\" -> \"OK\" to make it render correctly" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "db404b2e", - "metadata": {}, - "outputs": [], - "source": [ - "for version in weave.versions(points):\n", - " print(weave.expr(version))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "caa2daef", - "metadata": {}, - "outputs": [], - "source": [ - "plot = weave.legacy.panels.Plot(weave.legacy.ops.compare_versions(points))\n", - "plot.set_x(lambda row: row['x'])\n", - "plot.set_y(lambda row: row['y'])\n", - "plot.set_label(lambda row: row['version'])\n", - "weave.show(plot)\n", - "\n", - "# NOTE! There is a UI bug here. You need to click the gear icon and\n", - "# then \"Reset & Automate Plot\" -> \"OK\" to make it render correctly" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/experimental/old_openai_monitoring.ipynb b/examples/experimental/old_openai_monitoring.ipynb deleted file mode 100644 index 5cc04cbf307..00000000000 --- a/examples/experimental/old_openai_monitoring.ipynb +++ /dev/null @@ -1,226 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "30ccfdbc", - "metadata": {}, - "source": [ - "This notebook shows how to use our openai logging integration to monitor api calls" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8107732a-fb90-45f8-8377-6381bd28475d", - "metadata": {}, - "outputs": [], - "source": [ - "import weave\n", - "weave.use_frontend_devmode()\n", - "from weave.legacy.monitoring import openai, init_monitor\n", - "\n", - "OPENAI_MODEL = 'gpt-3.5-turbo'" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ba2b2070", - "metadata": {}, - "outputs": [], - "source": [ - "# Pass //\n", - "m = init_monitor('shawn/oai-mon/test21')\n", - "\n", - "# Do an initial request, otherwise we don't have a type on which to recommend the OpenAI board!\n", - "# We need at least 2 requests for the Board to work, otherwise we get divide by zero errors.\n", - "# TODO: fix this onboarding issue\n", - "r = openai.ChatCompletion.create(model=OPENAI_MODEL, messages=[{\"role\": \"user\", \"content\": f\"hello world!\"}])\n", - "r = openai.ChatCompletion.create(model=OPENAI_MODEL, messages=[{\"role\": \"user\", \"content\": f\"what is 2+2?\"}])" - ] - }, - { - "cell_type": "markdown", - "id": "fb51abb4", - "metadata": {}, - "source": [ - "Click the link above to go to the Weave UI for the table we're logging to.\n", - "\n", - "From there you can click \"OpenAI Monitor Board\" to create a Weave Board for this data stream." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "e711d521", - "metadata": {}, - "outputs": [], - "source": [ - "# Monitor ChatCompletion requests\n", - "r = openai.ChatCompletion.create(model=OPENAI_MODEL, messages=[\n", - " {\"role\": \"user\", \"content\": f\"who won the world series in 2006?\"},\n", - " ])\n", - "r['choices'][0]['message']['content']" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "7930e774", - "metadata": {}, - "outputs": [], - "source": [ - "# To keep track of prompts and parameters, add them to attributes on the logged\n", - "# record.\n", - "\n", - "system_prompt = \"you always write in bullet points\"\n", - "prompt = 'solve the following equation step by step: {equation}'\n", - "params = {'equation': '4 * (3 - 1)'}\n", - "openai.ChatCompletion.create(model=OPENAI_MODEL,\n", - " messages=[\n", - " {\"role\": \"system\", \"content\": system_prompt},\n", - " {\"role\": \"user\", \"content\": prompt.format(**params)},\n", - " ],\n", - " # you can add additional attributes to the logged record\n", - " # see the monitor_api notebook for more examples\n", - " monitor_attributes={\n", - " 'system_prompt': system_prompt,\n", - " 'prompt': prompt,\n", - " 'params': params\n", - " })" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b8b423df", - "metadata": {}, - "outputs": [], - "source": [ - "# Monitor streaming requests\n", - "# TODO: we don't get token counts here yet.\n", - "from weave.legacy.monitoring.openai import message_from_stream\n", - "r = openai.ChatCompletion.create(model=OPENAI_MODEL, messages=[\n", - " {\"role\": \"system\", \"content\": \"Your are a robot and only speak in robot, like beep bloop bop.\"},\n", - " {\"role\": \"user\", \"content\": f\"Tell me a 50 word story.\"},\n", - " ], stream=True)\n", - "for s in message_from_stream(r):\n", - " print(s, end='')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "bca62fdb", - "metadata": {}, - "outputs": [], - "source": [ - "# Render table inline\n", - "#m.rows()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "bdae9ba5-aa2b-4191-9a42-c1078ba0698a", - "metadata": {}, - "outputs": [], - "source": [ - "# Render board inline\n", - "# from weave.legacy.panels_py import panel_llm_monitor\n", - "# board = panel_llm_monitor.board.raw_resolve_fn(m.rows())\n", - "# board" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d237a885", - "metadata": {}, - "outputs": [], - "source": [ - "# Other examples\n", - "\n", - "# TODO: restore async variant\n", - "\n", - "# result = await monitored_a_create(model=\"gpt-3.5-turbo\", messages=[\n", - "# {\"role\": \"system\", \"content\": \"You are a world-class machine learning researcher.\"},\n", - "# {\"role\": \"user\", \"content\": f\"Please provide a simple, fact-based question to send to an AI system. Do not say anything other than the question itself. Use this random number as inspiration: {random.random()}.\"},\n", - "# ])\n", - "# result\n", - "\n", - "# TODO: ensure works with function calls\n", - "# functions = [\n", - "# {\n", - "# \"name\": \"get_current_weather\",\n", - "# \"description\": \"Get the current weather\",\n", - "# \"parameters\": {\n", - "# \"type\": \"object\",\n", - "# \"properties\": {\n", - "# \"location\": {\n", - "# \"type\": \"string\",\n", - "# \"description\": \"The city and state, e.g. San Francisco, CA\",\n", - "# },\n", - "# \"format\": {\n", - "# \"type\": \"string\",\n", - "# \"enum\": [\"celsius\", \"fahrenheit\"],\n", - "# \"description\": \"The temperature unit to use. Infer this from the users location.\",\n", - "# },\n", - "# },\n", - "# \"required\": [\"location\", \"format\"],\n", - "# },\n", - "# },\n", - "# {\n", - "# \"name\": \"get_n_day_weather_forecast\",\n", - "# \"description\": \"Get an N-day weather forecast\",\n", - "# \"parameters\": {\n", - "# \"type\": \"object\",\n", - "# \"properties\": {\n", - "# \"location\": {\n", - "# \"type\": \"string\",\n", - "# \"description\": \"The city and state, e.g. San Francisco, CA\",\n", - "# },\n", - "# \"format\": {\n", - "# \"type\": \"string\",\n", - "# \"enum\": [\"celsius\", \"fahrenheit\"],\n", - "# \"description\": \"The temperature unit to use. Infer this from the users location.\",\n", - "# },\n", - "# \"num_days\": {\n", - "# \"type\": \"integer\",\n", - "# \"description\": \"The number of days to forecast\",\n", - "# }\n", - "# },\n", - "# \"required\": [\"location\", \"format\", \"num_days\"]\n", - "# },\n", - "# },\n", - "# ]\n", - "\n", - "# result = openai.ChatCompletion.create(model=\"gpt-3.5-turbo\", functions=functions, messages=[\n", - "# {\"role\": \"system\", \"content\": \"You love to call functions.\"},\n", - "# {\"role\": \"user\", \"content\": f\"what's the weather today\"},\n", - "# ])" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/experimental/openai_monitoring.ipynb b/examples/experimental/openai_monitoring.ipynb deleted file mode 100644 index 0db4b93e1c3..00000000000 --- a/examples/experimental/openai_monitoring.ipynb +++ /dev/null @@ -1,239 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## OpenAI Monitoring Examples\n", - "\n", - "This updates our monitoring code to:\n", - "1. Be compatible with `openai>=1.0.0`\n", - "2. Use patching (instead of forcing the user to import from our custom impl)\n", - "3. Use a callback system to add new functionality before/after certain stages" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Setup" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import openai\n", - "import weave\n", - "from weave.legacy.monitoring.openai import patch, unpatch\n", - "\n", - "weave.legacy.monitoring.openai.patch()\n", - "# weave.legacy.monitoring.openai.unpatch()\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Advanced Setup\n", - "You can customize callbacks (e.g. which StreamTable to log to) by explicitly passing callbacks to `patch`" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# from weave.legacy.monitoring.openai import ReassembleStream, LogToStreamTable\n", - "\n", - "# weave.legacy.monitoring.openai.patch(\n", - "# callbacks=[LogToStreamTable.from_stream_name(\"stream\", \"project\", \"entity\")]\n", - "# )\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Module-level Sync Completion" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "result = openai.chat.completions.create(\n", - " model=\"gpt-3.5-turbo\",\n", - " messages=[{\"role\": \"system\", \"content\": \"Tell me a joke\"}],\n", - ")\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "result\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Sync Completion" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "client = openai.OpenAI()\n", - "\n", - "result = client.chat.completions.create(\n", - " model=\"gpt-3.5-turbo\",\n", - " messages=[{\"role\": \"system\", \"content\": \"Tell me a joke\"}],\n", - ")\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "result\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Sync Completion (Streaming)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "client = openai.OpenAI()\n", - "\n", - "stream = client.chat.completions.create(\n", - " model=\"gpt-3.5-turbo\",\n", - " messages=[{\"role\": \"system\", \"content\": \"Tell me a joke\"}],\n", - " stream=True\n", - ")\n", - "\n", - "for x in stream:\n", - " print(x)\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Async Completion" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "client = openai.AsyncOpenAI()\n", - "\n", - "result = await client.chat.completions.create(\n", - " model=\"gpt-3.5-turbo\",\n", - " messages=[{\"role\": \"system\", \"content\": \"Tell me a joke\"}],\n", - ")\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "result\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Async Completion (Streaming)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "client = openai.AsyncOpenAI()\n", - "\n", - "stream = await client.chat.completions.create(\n", - " model=\"gpt-3.5-turbo\",\n", - " messages=[{\"role\": \"system\", \"content\": \"Tell me a joke\"}],\n", - " stream=True\n", - ")\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "async for x in stream:\n", - " ...\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "weave310", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.11" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/examples/experimental/prompts_dev/generate_synth_mon_board.ipynb b/examples/experimental/prompts_dev/generate_synth_mon_board.ipynb deleted file mode 100644 index 96454814a2f..00000000000 --- a/examples/experimental/prompts_dev/generate_synth_mon_board.ipynb +++ /dev/null @@ -1,192 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "4549831c", - "metadata": {}, - "source": [ - "\"Weights\n", - "\n", - "\n", - "\n", - " \"Open\n", - "\n", - "\n", - "# Generate Synthetic Data for Large-Scale LLM Monitoring Demo\n", - "\n", - "This notebook generates synthetic data (Shakespearean dialogue) in the format used by the monitoring.openai integration.\n", - "See the openai_monitoring notebook for instructions for logging real OpenAI API calls.\n", - "\n", - "# Step 0: Setup\n", - "\n", - "Import dependencies" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "00647082", - "metadata": {}, - "outputs": [], - "source": [ - "!pip install -qqq weave\n", - "\n", - "import uuid\n", - "from datetime import timedelta\n", - "import weave\n", - "from weave.legacy import ops_arrow\n", - "from weave.legacy.monitoring import monitor\n", - "from weave.syndata_mon import random_predictions" - ] - }, - { - "cell_type": "markdown", - "id": "a8b49310-4d7a-4f3f-a901-e1f9e87baff8", - "metadata": {}, - "source": [ - "# Step 1: Generate random predictions and montior call spans" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "93653c45", - "metadata": {}, - "outputs": [], - "source": [ - "preds = random_predictions(100)\n", - "\n", - "# Convert synthetic data into the format used by the weave.legacy.monitoring.openai integration\n", - "\n", - "# convert model_version in the synthetic data to an openai model version\n", - "# this makes it so that there's a new API key that has appeared in our logs recently, and that key\n", - "# has started using gpt-4 which makes a cost spike\n", - "versions = sorted(preds.column('model_version').unique())\n", - "version_map = {}\n", - "for i, v in enumerate(reversed(versions)):\n", - " api_key = 'sk-U4...yK7z'\n", - " model = 'gpt-3.5-turbo-0613'\n", - " if i == 1 or i == 2:\n", - " # second and third most recent versions use a different api key\n", - " api_key = 'sk-U9...a22c'\n", - " if i == 1:\n", - " # second most recent version uses gpt-4\n", - " model = 'gpt-4-0613'\n", - " version_map[v] = (api_key, model)\n", - " \n", - "spans = [] \n", - "for i, pred in enumerate(preds):\n", - " api_key, model = version_map[pred['model_version']]\n", - " latency_mult = 1\n", - " if model == 'gpt-4-0613':\n", - " latency_mult = 3\n", - " span = monitor.Span('openai.api_resources.chat_completion.type.create',\n", - " inputs={\n", - " 'messages':[\n", - " {\"role\": \"user\", \"content\": pred['prompt']}\n", - " ]\n", - " },\n", - " output={\n", - " 'id': 'chatcmpl-%s' % uuid.uuid4(),\n", - " 'object': 'chat.completion',\n", - " 'created': pred['timestamp'].timestamp(),\n", - " 'model': model,\n", - " 'choices': [\n", - " {\n", - " 'index': 0,\n", - " 'message': {\n", - " 'role': 'assistant',\n", - " 'content': pred['completion']\n", - " },\n", - " 'finish_reason': 'stop'\n", - " }\n", - " ],\n", - "\n", - " },\n", - " attributes={\n", - " 'api_key': api_key,\n", - " 'username': pred['username']\n", - " },\n", - " summary={\n", - " 'prompt_tokens': pred['prompt_tokens'],\n", - " 'completion_tokens': pred['completion_tokens'],\n", - " 'total_tokens': (pred['prompt_tokens'] + pred['completion_tokens'])\n", - " })\n", - " span.start_time = pred['timestamp']\n", - " span.end_time = pred['timestamp'] + timedelta(seconds=pred['latency'] * latency_mult)\n", - " spans.append({'timestamp': pred['timestamp'], **span.asdict()})" - ] - }, - { - "cell_type": "markdown", - "id": "88479dbd-6fae-4353-974e-5e13704f720f", - "metadata": {}, - "source": [ - "# Step 2: Save and view synthetic data" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9548c9aa", - "metadata": {}, - "outputs": [], - "source": [ - "# Save as a local table\n", - "oai_data = weave.save(ops_arrow.to_arrow(spans), 'oai_data')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b59be0b5", - "metadata": {}, - "outputs": [], - "source": [ - "oai_data" - ] - }, - { - "cell_type": "markdown", - "id": "46669b4f-c569-40ee-a6fe-c5f2fdf64eb7", - "metadata": {}, - "source": [ - "# Step 3: View LLM Monitoring Board with synthetic data" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "7045f1eb", - "metadata": {}, - "outputs": [], - "source": [ - "# Use the llm monitoring template to visualize the data\n", - "from weave.legacy.panels_py import panel_llm_monitor\n", - "board = panel_llm_monitor.board.raw_resolve_fn(oai_data)\n", - "board" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/experimental/prompts_dev/synthetic_openai_data.ipynb b/examples/experimental/prompts_dev/synthetic_openai_data.ipynb deleted file mode 100644 index c63c29d4311..00000000000 --- a/examples/experimental/prompts_dev/synthetic_openai_data.ipynb +++ /dev/null @@ -1,167 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "4549831c", - "metadata": {}, - "source": [ - "\"Weights\n", - "\n", - "# Generate Synthetic Trace Data\n", - "\n", - "Generate some synthetic OpenAI data we can explore in a LLM Monitoring Debugging Board." - ] - }, - { - "cell_type": "markdown", - "id": "e3d9305e", - "metadata": {}, - "source": [ - "# Step 0: Setup" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "00647082", - "metadata": {}, - "outputs": [], - "source": [ - "import uuid\n", - "from datetime import timedelta\n", - "import weave\n", - "\n", - "from weave.legacy import ops_arrow\n", - "from weave.legacy.monitoring import monitor, StreamTable" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "93041e2e", - "metadata": {}, - "outputs": [], - "source": [ - "from weave.syndata_mon import random_predictions" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "61b9c12a", - "metadata": {}, - "outputs": [], - "source": [ - "WB_ENTITY = # replace with your W&B username or team name\n", - "WB_PROJECT = \"weave\"\n", - "WB_STREAM = \"synthetic_openai_stream\"" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "93653c45", - "metadata": {}, - "outputs": [], - "source": [ - "preds = random_predictions(10)\n", - "\n", - "# Convert synthetic data into the format used by the weave.legacy.monitoring.openai integration\n", - "\n", - "# convert model_version in the synthetic data to an openai model version\n", - "# this makes it so that there's a new API key that has appeared in our logs recently, and that key\n", - "# has started using gpt-4 which makes a cost spike\n", - "versions = sorted(preds.column('model_version').unique())\n", - "version_map = {}\n", - "for i, v in enumerate(reversed(versions)):\n", - " api_key = 'sk-U4...yK7z'\n", - " model = 'gpt-3.5-turbo-0613'\n", - " if i == 1 or i == 2:\n", - " # second and third most recent versions use a different api key\n", - " api_key = 'sk-U9...a22c'\n", - " if i == 1:\n", - " # second most recent version uses gpt-4\n", - " model = 'gpt-4-0613'\n", - " version_map[v] = (api_key, model)\n", - " \n", - "spans = [] \n", - "for i, pred in enumerate(preds):\n", - " api_key, model = version_map[pred['model_version']]\n", - " latency_mult = 1\n", - " if model == 'gpt-4-0613':\n", - " latency_mult = 3\n", - " span = monitor.Span('openai.api_resources.chat_completion.type.create',\n", - " inputs={\n", - " 'messages':[\n", - " {\"role\": \"user\", \"content\": pred['prompt']}\n", - " ]\n", - " },\n", - " output={\n", - " 'id': 'chatcmpl-%s' % uuid.uuid4(),\n", - " 'object': 'chat.completion',\n", - " 'created': pred['timestamp'].timestamp(),\n", - " 'model': model,\n", - " 'choices': [\n", - " {\n", - " 'index': 0,\n", - " 'message': {\n", - " 'role': 'assistant',\n", - " 'content': pred['completion']\n", - " },\n", - " 'finish_reason': 'stop'\n", - " }\n", - " ],\n", - "\n", - " },\n", - " attributes={\n", - " 'api_key': api_key,\n", - " 'username': pred['username']\n", - " },\n", - " summary={\n", - " 'prompt_tokens': pred['prompt_tokens'],\n", - " 'completion_tokens': pred['completion_tokens'],\n", - " 'total_tokens': (pred['prompt_tokens'] + pred['completion_tokens'])\n", - " })\n", - " span.start_time = pred['timestamp']\n", - " span.end_time = pred['timestamp'] + timedelta(seconds=pred['latency'] * latency_mult)\n", - " spans.append({'timestamp': pred['timestamp'], **span.asdict()})" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9548c9aa", - "metadata": {}, - "outputs": [], - "source": [ - "st = StreamTable(f\"{WB_ENTITY}/{WB_PROJECT}/{WB_STREAM}\")\n", - "\n", - "for span in spans:\n", - " st.log(span)\n", - "\n", - "st.finish()\n" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/experimental/skip_test/04 Tutorial - Publishing Data and Ops.ipynb b/examples/experimental/skip_test/04 Tutorial - Publishing Data and Ops.ipynb deleted file mode 100644 index 1e5591b2b11..00000000000 --- a/examples/experimental/skip_test/04 Tutorial - Publishing Data and Ops.ipynb +++ /dev/null @@ -1,125 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "b174bac3", - "metadata": {}, - "outputs": [], - "source": [ - "import weave\n", - "from typing import Any\n", - "\n", - "@weave.op()\n", - "def inc(a: int, delta: int = 1) -> int:\n", - " return a + delta\n", - "\n", - "# store x in local dir\n", - "x = weave.save(10, \"local-num\")\n", - "print(x)\n", - "# store y as a remote artifact - this is stateful and will create\n", - "# new versions if you've previously saved this artifact\n", - "y = weave.publish(100, \"remote-num\")\n", - "print(y)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "1949651f", - "metadata": {}, - "outputs": [], - "source": [ - "# increment constant\n", - "print(weave.use(inc(1)))\n", - "# increment local number - DO NOT SAVE\n", - "print(weave.use(inc(x)))\n", - "# increment remote number - DO NOT SAVE\n", - "print(weave.use(inc(y)))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "efd9ed1c", - "metadata": {}, - "outputs": [], - "source": [ - "# this might take a minute since constructing w&b artifacts is a bit slow atm\n", - "for i in range(0, 5):\n", - " y = weave.use(inc(y, 1))\n", - " # publish result back to remote-num\n", - " weave.publish(y, \"remote-num\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "35a0e593", - "metadata": {}, - "outputs": [], - "source": [ - "# TODO: this doesn't work yet, it only returns the current version\n", - "for version in weave.versions(y):\n", - " print(version)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9d6a69fc", - "metadata": {}, - "outputs": [], - "source": [ - "# we can also publish ops\n", - "remote_op = weave.publish(inc)\n", - "print(remote_op)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "7548b05f", - "metadata": {}, - "outputs": [], - "source": [ - "# this cell can be run from a full kernel restart and it should work\n", - "import weave\n", - "import shutil\n", - "from weave.legacy.artifacts_local import LOCAL_ARTIFACT_DIR\n", - "from weave import uris\n", - "\n", - "try:\n", - " shutil.rmtree(LOCAL_ARTIFACT_DIR)\n", - "except FileNotFoundError:\n", - " pass\n", - "\n", - "remote_y = uris.WeaveURI.parse('wandb-artifact://jzhao/weave_ops/remote-num:v0').to_ref().get()\n", - "remote_inc = uris.WeaveURI.parse('wandb-artifact://jzhao/weave_ops/op-def-op-inc:v0').to_ref().get()\n", - "\n", - "remote_y" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/experimental/skip_test/Composable Python panels.ipynb b/examples/experimental/skip_test/Composable Python panels.ipynb deleted file mode 100644 index 22400d2130f..00000000000 --- a/examples/experimental/skip_test/Composable Python panels.ipynb +++ /dev/null @@ -1,79 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "6f2db98c", - "metadata": {}, - "source": [ - "## This demo is not currently working" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5b582daf", - "metadata": {}, - "outputs": [], - "source": [ - "import weave" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "06ea5a95", - "metadata": {}, - "outputs": [], - "source": [ - "container = weave.legacy.panels.Container()\n", - "\n", - "# A couple variables to hold our slider values (this is unnecessary boilerplate that I can\n", - "# get rid of)\n", - "var_a = container.config.add_variable('slider_value_a', weave.types.Number(), 5)\n", - "var_b = container.config.add_variable('slider_value_b', weave.types.Number(), 19)\n", - "\n", - "# Create two sliders in the Container\n", - "container.config.add_panel(\n", - " weave.legacy.panels.Slider(input_node=var_a, config=weave.legacy.panels.SliderConfig(0, 100, 0.1)))\n", - "container.config.add_panel(\n", - " weave.legacy.panels.Slider(input_node=var_b, config=weave.legacy.panels.SliderConfig(-1000, 1000, 1)))\n", - "\n", - "# Use a Number panel to show the results of adding the two sliders together\n", - "container.config.add_panel(\n", - " weave.legacy.panels.Number(input_node=var_a + var_b)\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9423cf32", - "metadata": {}, - "outputs": [], - "source": [ - "weave.show(container)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/experimental/skip_test/Confusion Matrix.ipynb b/examples/experimental/skip_test/Confusion Matrix.ipynb deleted file mode 100644 index b357921adfa..00000000000 --- a/examples/experimental/skip_test/Confusion Matrix.ipynb +++ /dev/null @@ -1,288 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "51500f80", - "metadata": {}, - "outputs": [], - "source": [ - "import random\n", - "import typing\n", - "import itertools\n", - "from PIL import Image\n", - "import weave\n", - "from weave.legacy import storage\n", - "from weave import weave_internal\n", - "#weave.use_frontend_devmode()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b6e5e1c9", - "metadata": {}, - "outputs": [], - "source": [ - "# Not really model predictions but it'll work for now\n", - "import string\n", - "import hashlib\n", - "\n", - "def simple_hash(n, b):\n", - " return int.from_bytes(hashlib.sha256(str(n).encode()).digest(), \"little\") % b\n", - "\n", - "def create_data(n_rows, n_extra_cols, images=False):\n", - " inner_count = int(n_rows / 25)\n", - " base_im = Image.linear_gradient('L').resize((32, 32))\n", - " x_choices = string.ascii_lowercase\n", - " extra_cols = [chr(ord('a') + i) for i in range(n_extra_cols)]\n", - " ims = []\n", - " for i, (rotate, shear, _) in enumerate(\n", - " itertools.product(range(5), range(5), range(inner_count))\n", - " ):\n", - " im ={\n", - " 'rotate': rotate,\n", - " 'shear': shear,\n", - " 'y': x_choices[simple_hash(i**13, 5)],\n", - " 'x': x_choices[simple_hash(i**13, 11)]\n", - " }\n", - " if images:\n", - " im['image'] = (base_im\n", - " .rotate(rotate * 4)\n", - " .transform((32, 32), Image.AFFINE, (1, shear / 10, 0, 0, 1, 0), Image.BICUBIC))\n", - " for j, col in enumerate(extra_cols):\n", - " im[col] = x_choices[simple_hash(i*13**j, 11)]\n", - " ims.append(im)\n", - " return ims\n", - "\n", - "ims = create_data(100, 1, True)\n", - "#ims = storage.to_arrow(ims)\n", - "#ims = weave.save(ims)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "2da45914", - "metadata": {}, - "outputs": [], - "source": [ - "weave.show(ims)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "49501ff4", - "metadata": {}, - "outputs": [], - "source": [ - "plot = weave.legacy.panels.Plot(ims)\n", - "plot.set_x(lambda row: row['rotate'])\n", - "plot.set_y(lambda row: row['x'])\n", - "plot.set_tooltip(lambda row: row['image'])\n", - "weave.show(plot)" - ] - }, - { - "cell_type": "markdown", - "id": "6583e01c", - "metadata": {}, - "source": [ - "## Facet is fun!\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "090d8ab8", - "metadata": {}, - "outputs": [], - "source": [ - "# This is one way to build a multi-confusion matrix.\n", - "\n", - "facet = weave.legacy.panels.Facet(\n", - " input_node=ims,\n", - " x=lambda im: im['rotate'],\n", - " y=lambda im: im['shear'],\n", - " select=lambda cell: weave.legacy.panels.Plot(\n", - " input_node=cell.groupby(lambda row: row['y']),\n", - " x=lambda group: group.count(),\n", - " y=lambda group: group.key(),\n", - " label=lambda group: group.key(),\n", - " tooltip=lambda group: group.map(lambda r: r['image']),\n", - " mark='bar',\n", - " no_axes=True,\n", - " no_legend=True\n", - " )\n", - ")\n", - "\n", - "weave.show(facet)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "e987f626", - "metadata": {}, - "outputs": [], - "source": [ - "# @weave.op()\n", - "# def confusion_matrix(inp: typing.Any, guess_col: str, truth_col: str, compare_col: str) -> weave.legacy.panels.Facet:\n", - "# return weave.legacy.panels.Facet(\n", - "# input_node=inp,\n", - "# x=lambda i: i[guess_col],\n", - "# y=lambda i: i[truth_col],\n", - "# select=lambda cell: weave.legacy.panels.Plot(\n", - "# input_node=cell.groupby(\n", - "# weave.define_fn({'row': cell.type.object_type}, lambda row: row[compare_col])),\n", - "# x=lambda group: group.count(),\n", - "# y=lambda group: group.key(),\n", - "# label=lambda group: group.key(),\n", - "# mark='bar',\n", - "# no_axes=True,\n", - "# no_legend=True\n", - "# )\n", - "# )\n", - "\n", - "# An example of a Panel returning op. This (sort of) works but there are lots of \n", - "\n", - "@weave.op()\n", - "def confusion_matrix(inp: typing.Any, guess_col: str, truth_col: str, compare_col: str) -> weave.legacy.panels.Facet:\n", - " return weave.legacy.panels.Facet(\n", - " input_node=inp,\n", - " x=lambda i: i[guess_col],\n", - " y=lambda i: i[truth_col],\n", - " select=lambda cell: cell.count()\n", - " )" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9c800bac", - "metadata": {}, - "outputs": [], - "source": [ - "demos.confusion_matrix(ims, 'rotate', 'x', 'y')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "7eff6907", - "metadata": {}, - "outputs": [], - "source": [ - "# Other explorations\n", - "\n", - "\n", - "# weave.show(facet)\n", - "\n", - "# # Not working yet, but playing with removing lambdas\n", - "# facet = weave.legacy.panels.Facet(\n", - "# input_node=ims,\n", - "# x=ims['rotate'],\n", - "# y=ims['shear'],\n", - "# select=lambda cell: weave.legacy.panels.Plot(\n", - "# input_node=cell.groupby(cell.row['y']),\n", - "# x=lambda group: group.count(),\n", - "# y=lambda group: group.key(),\n", - "# label=lambda group: group.key(),\n", - "# mark='bar',\n", - "# no_axes=True,\n", - "# no_legend=True\n", - "# )\n", - "# )\n", - "\n", - "# # Maybe another cool thing\n", - "# facet = weave.legacy.panels.Facet(\n", - "# input_node=ims,\n", - "# x=lambda im: im['rotate'],\n", - "# y=lambda im: im['shear'],\n", - "# select=lambda cell_ims: cell_ims.groupby(cell.row['y'])\n", - "# .Plot(\n", - "# x=lambda group: group.count(),\n", - "# y=lambda group: group.key(),\n", - "# label=lambda group: group.key(),\n", - "# mark='bar',\n", - "# no_axes=True,\n", - "# no_legend=True\n", - "# )\n", - "# )\n", - "\n", - "# # OK actually its more like we just always want to drop the first argument\n", - "# # This is finally minimal, but we can't get autocomplete help.\n", - "# facet = ims.Facet(\n", - "# x=select('rotate'),\n", - "# y=select('shear'),\n", - "# select=groupby('y')\n", - "# .Plot(\n", - "# x=count(),\n", - "# y=group_key(),\n", - "# label=group_key(),\n", - "# mark=select('bar'),\n", - "# no_axes=True,\n", - "# no_legend=True\n", - "# )\n", - "# )\n", - "\n", - "# # Hmm.\n", - "# facet = ims\n", - "# .groupby(\n", - "# facet_x=select('rotate'),\n", - "# facet_y=select('shear'),\n", - "# y=select('run'))\n", - "# .count()\n", - "# .Plot()\n", - "\n", - "# facet = Plot(\n", - "# ims,\n", - "# facet_x=select('rotate'),\n", - "# facet_y=select('shear'),\n", - "# y=select('run')\n", - "# x=count()\n", - ")\n", - "\n", - "# TODO:\n", - "# - get rid of lambdas (ims.row could be a variable?)\n", - "# ... or we could just treat ims as the row variable when its assigned to a plot?\n", - "# - This is why react is a little nicer, component control flow isn't usually hidden\n", - "# away inside other components (framework style). Instead, you decide how you want to lay stuff\n", - "# out...\n", - "# - But we can achieve that here... We just need to make some lower level components.\n", - "# (Try IT!) Instead of PanelFacet, use PanelLayout or PanelGrid or something\n", - "# - see if we can get rid of groupby requirement in PanelPlot. You don't need it in Vega\n", - "# - also maybe look at Altair API?\n", - "#\n", - "# In react you'd do:\n", - "# const ConfusionMatrix = (ims) => (\n", - "# \n", - "# \n", - "# \n", - "# )" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/experimental/skip_test/Custom ops.ipynb b/examples/experimental/skip_test/Custom ops.ipynb deleted file mode 100644 index 0b0ee07a208..00000000000 --- a/examples/experimental/skip_test/Custom ops.ipynb +++ /dev/null @@ -1,149 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "f6c524b9", - "metadata": {}, - "outputs": [], - "source": [ - "import weave\n", - "import math\n", - "weave.use_frontend_devmode()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a257651b", - "metadata": {}, - "outputs": [], - "source": [ - "# Create our own useful op.\n", - "\n", - "import math\n", - "\n", - "@weave.op()\n", - "def my_func(x: float) -> float:\n", - " return math.sin(x / 0.2)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "89b6c0db", - "metadata": {}, - "outputs": [], - "source": [ - "# Now show it.\n", - "# Weave tries to guess the best way to show your data. Its first guess is not very good yet, so let's teach it!\n", - "# Click the gear icon to configure the Panel\n", - "# Try \"List of: Number\". You can page left to right using the controls that the bottom.\n", - "# Switch to \"Plot\"\n", - "# Pick \"row\" as the x value (todo this should be called \"i\" or \"item\" instead of row)\n", - "# Pick \"row * row\" as the y value.\n", - "# Cool, we made a curve!\n", - "\n", - "\n", - "x = [i / 1000.0 for i in range(0, 1000)]\n", - "\n", - "plot = weave.legacy.panels.Plot(x)\n", - "plot.set_x(lambda row: row)\n", - "plot.set_y(lambda row: my_func(row))\n", - "weave.show(plot)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3d97c0fd", - "metadata": {}, - "outputs": [], - "source": [ - "run_data = []\n", - "xs = [i / 1000.0 for i in range(0, 1000)]\n", - "for run in range(3):\n", - " run_data.append({\n", - " 'id': 'id-%s' % run,\n", - " 'x': xs,\n", - " 'y': [math.sin(10 * x / (run+1)) for x in xs]\n", - " })\n", - "plot = weave.legacy.panels.Plot(run_data)\n", - "plot.set_x(lambda row: row['x'])\n", - "plot.set_y(lambda row: row['y'])\n", - "plot.set_label(lambda row: row['id'])\n", - "weave.show(plot)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5f9b0741", - "metadata": {}, - "outputs": [], - "source": [ - "import pandas as pd\n", - "df = pd.DataFrame()\n", - "df['x'] = [i / 1000.0 for i in range(0, 1000)]\n", - "df['y1'] = df['x'].map(lambda x: math.sin(10 * x / 1))\n", - "df['y2'] = df['x'].map(lambda x: math.sin(10 * x / 2))\n", - "df['y3'] = df['x'].map(lambda x: math.sin(10 * x / 3))\n", - "\n", - "# df.melt converts to a table with three columns: x, series, y\n", - "df = df.melt(id_vars=['x'], value_vars=['y1', 'y2', 'y3'], var_name='series', value_name='y')\n", - "\n", - "multiple_y = df.to_dict('records')\n", - "plot = weave.legacy.panels.Plot(multiple_y)\n", - "plot.set_x(lambda row: row['x'])\n", - "plot.set_y(lambda row: row['y'])\n", - "plot.set_label(lambda row: row['series'])\n", - "plot.set_mark('line')\n", - "weave.show(plot)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "07752656", - "metadata": {}, - "outputs": [], - "source": [ - "import pandas as pd\n", - "df = pd.DataFrame()\n", - "df['x'] = [i / 1000.0 for i in range(0, 1000)]\n", - "df['y1'] = df['x'].map(lambda x: math.sin(10 * x / 1))\n", - "df['y2'] = df['x'].map(lambda x: math.sin(10 * x / 2))\n", - "df['y3'] = df['x'].map(lambda x: math.sin(10 * x / 3))\n", - "multiple_y = df.to_dict('records')\n", - "\n", - "plot = weave.legacy.panels.Plot(multiple_y)\n", - "plot.set_x(lambda row: row['x'])\n", - "plot.set_y(lambda row: [row['y1'], row['y2'], row['y3']])\n", - "plot.set_label(lambda row: ['y1', 'y2', 'y3'])\n", - "plot.set_mark('line')\n", - "weave.show(plot)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/experimental/skip_test/Diffusion explore 2.ipynb b/examples/experimental/skip_test/Diffusion explore 2.ipynb deleted file mode 100644 index 1e3dfee050e..00000000000 --- a/examples/experimental/skip_test/Diffusion explore 2.ipynb +++ /dev/null @@ -1,101 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "00411857", - "metadata": {}, - "outputs": [], - "source": [ - "import weave\n", - "from weave import weave_internal\n", - "from weave.legacy.ecosystem.all import langchain\n", - "weave.use_frontend_devmode()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "30c46465", - "metadata": {}, - "outputs": [], - "source": [ - "weave.legacy.panels.Board(\n", - " vars={\n", - " \"chat_model\": langchain.chat_openai(\"gpt-3.5-turbo\", 0.7),\n", - " \"animals\": weave.legacy.panels.Query(\n", - " [\"koala\", \"giraffe\", \"leopard\"],\n", - " text=lambda item: item\n", - " ),\n", - " \"objects\": weave.legacy.panels.Query(\n", - " [\"spoon\", \"guitar\"],\n", - " text=lambda item: item\n", - " ),\n", - " \"prompts\": weave.legacy.panels.Query([\n", - " \"A {animal} with an {object}\",\n", - " \"A 20-word story about a {animal} with an {object}\",\n", - " \"You are creative writer. Describe a scene with a {animal} and a {object}. Choose an art style and use adjectives that evoke it.\"\n", - " ],\n", - " text=lambda item: item),\n", - " \"combos\": lambda animals, objects, prompts:\n", - " weave.legacy.ops.cross_product(weave.legacy.ops.dict_(\n", - " animal=animals.pinned_data(),\n", - " object=objects.pinned_data(),\n", - " prompt=prompts.pinned_data())),\n", - "\n", - " },\n", - " panels=[\n", - " weave.legacy.panels.BoardPanel(\n", - " lambda chat_model, combos: weave.legacy.panels.Facet(\n", - " combos,\n", - " cell_size=(256, 256),\n", - " x=lambda item: item['animal'] + ' ' + item['object'],\n", - " y=lambda item: item['prompt'],\n", - " select=lambda items: \n", - " weave.legacy.panels.Group(\n", - " vars={\n", - " 'gen_text': chat_model.predict(\n", - " items[0]['prompt'].format(weave.legacy.ops.dict_(\n", - " animal=items[0]['animal'],\n", - " object=items[0]['object'],\n", - " ))),\n", - " },\n", - " items={\n", - " 'text': lambda gen_text: gen_text,\n", - " 'image': lambda gen_text: gen_text.stable_diffusion()\n", - " }\n", - " )\n", - " ),\n", - " layout=weave.legacy.panels.BoardPanelLayout(x=0, y=0, w=24, h=24)\n", - " )\n", - "# weave.legacy.panels.BoardPanel(\n", - "# lambda col_names: weave.legacy.panels.Table(col_names, columns=[lambda col: col]),\n", - "# layout=weave.legacy.panels.BoardPanelLayout(x=0, y=12, w=12, h=6)\n", - "# ),\n", - "# weave.legacy.panels.BoardPanel(\n", - "# lambda row_names: weave.legacy.panels.Table(row_names, columns=[lambda row: row]),\n", - "# layout=weave.legacy.panels.BoardPanelLayout(x=12, y=12, w=12, h=6)\n", - "# ),\n", - " ]\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c8d3d23b", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/experimental/skip_test/Diffusion explore.ipynb b/examples/experimental/skip_test/Diffusion explore.ipynb deleted file mode 100644 index 0d3365f2f25..00000000000 --- a/examples/experimental/skip_test/Diffusion explore.ipynb +++ /dev/null @@ -1,169 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "6048a38a", - "metadata": {}, - "source": [ - "# Diffusion exploration\n", - "\n", - "Progammatically build a Weave Dashboard that generates a grid of increasingly magical images of unlikely pairs of animals.\n", - "\n", - "## Play with parameters\n", - "\n", - "* topic: change what the pairs are (animals? plants? minerals?)\n", - "* num_cols: change the number of levels/granularity of increasing magic\n", - "* num_rows: change how many groupings of the subject are generated :)\n", - "* prompts: edit the text of the prompt in col_names or row_names to modify this setup :)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "00411857", - "metadata": {}, - "outputs": [], - "source": [ - "import weave\n", - "from weave import weave_internal\n", - "from weave.legacy.ecosystem.all import langchain" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "30c46465", - "metadata": {}, - "outputs": [], - "source": [ - "weave.legacy.panels.Board(\n", - " vars={\n", - " \"chat_model\": langchain.chat_openai(\"gpt-3.5-turbo\", 0.7),\n", - " \"topic\": \"animals\",\n", - " \"num_cols\": 2,\n", - " \"col_names\": lambda chat_model, num_cols: weave.legacy.ops.range(0, num_cols, 1).map(\n", - " lambda i: chat_model.predict(weave_internal.const(\"You are a creative writer. You've come up with \")\n", - " + i.toString()\n", - " + \" ordered descriptive phrases (3 words or fewer) that represent how magical something is, from least magical to most. Phrase \"\n", - " + i.toString()\n", - " + \" is:\")\n", - " ),\n", - " \"num_rows\": 2,\n", - " \"row_names\": lambda chat_model, topic, num_rows: weave.legacy.ops.range(0, num_rows, 1).map(\n", - " lambda i: chat_model.predict(weave_internal.const(\"You are a creative writer. You've come up with \")\n", - " + i.toString()\n", - " + \" \" + topic\n", - " + \" that are unlikely to be seen together. Number \"\n", - " + i.toString()\n", - " + \" is: \")\n", - " ),\n", - "\n", - " },\n", - " panels=[\n", - " weave.legacy.panels.BoardPanel(\n", - " lambda num_rows, num_cols, row_names, col_names: weave.legacy.panels.Facet(\n", - " weave.legacy.ops.range(0, num_rows * num_cols, 1),\n", - " cell_size=(192, 192),\n", - " x=lambda i: col_names[i % num_cols],\n", - " y=lambda i: row_names[(i / num_cols).floor()],\n", - " select=lambda cell_values: (\n", - " weave_internal.const(\"cute hd cartoon of \")\n", - " + row_names[(cell_values[0] / num_cols).floor()]\n", - " + \" \"\n", - " + col_names[(cell_values[0] % num_cols)]).stable_diffusion()\n", - " ),\n", - " layout=weave.legacy.panels.BoardPanelLayout(x=0, y=0, w=24, h=12)\n", - " ),\n", - " weave.legacy.panels.BoardPanel(\n", - " lambda col_names: weave.legacy.panels.Table(col_names, columns=[lambda col: col]),\n", - " layout=weave.legacy.panels.BoardPanelLayout(x=0, y=12, w=12, h=6)\n", - " ),\n", - " weave.legacy.panels.BoardPanel(\n", - " lambda row_names: weave.legacy.panels.Table(row_names, columns=[lambda row: row]),\n", - " layout=weave.legacy.panels.BoardPanelLayout(x=12, y=12, w=12, h=6)\n", - " ),\n", - " ]\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c0f6057c", - "metadata": {}, - "outputs": [], - "source": [ - "weave.save(\n", - "\"\"\"\n", - "weave.legacy.panels.Board(\n", - " vars={\n", - " \"chat_model\": langchain.chat_openai(\"gpt-3.5-turbo\", 0.7),\n", - " \"num_cols\": 2,\n", - " \"col_names\": lambda chat_model, num_cols: weave.legacy.ops.range(0, num_cols, 1).map(\n", - " lambda i: chat_model.predict(weave_internal.const(\"You are a creative writer. You've come up with \")\n", - " + i.toString()\n", - " + \" ordered descriptive phrases (3 words or fewer) that represent how magical something is, from least magical to most. Phrase \"\n", - " + i.toString()\n", - " + \" is:\")\n", - " ),\n", - " \"num_rows\": 2,\n", - " \"row_names\": lambda chat_model, num_rows: weave.legacy.ops.range(0, num_rows, 1).map(\n", - " lambda i: chat_model.predict(weave_internal.const(\"You are a creative writer. You've come up with \")\n", - " + i.toString()\n", - " + \" animals that are unlikely to be seen together. Animal \"\n", - " + i.toString()\n", - " + \" is: \")\n", - " ),\n", - "\n", - " },\n", - " panels=[\n", - " weave.legacy.panels.BoardPanel(\n", - " lambda num_rows, num_cols, row_names, col_names: weave.legacy.panels.Facet(\n", - " weave.legacy.ops.range(0, num_rows * num_cols, 1),\n", - " cell_size=(192, 192),\n", - " x=lambda i: col_names[i % num_cols],\n", - " y=lambda i: row_names[(i / num_cols).floor()],\n", - " select=lambda cell_values: (\n", - " weave_internal.const(\"cute hd cartoon of \")\n", - " + row_names[(cell_values[0] / num_cols).floor()]\n", - " + \" \"\n", - " + col_names[(cell_values[0] % num_cols)]).stable_diffusion()\n", - " ),\n", - " layout=weave.legacy.panels.BoardPanelLayout(x=0, y=0, w=24, h=12)\n", - " ),\n", - " weave.legacy.panels.BoardPanel(\n", - " lambda col_names: weave.legacy.panels.Table(col_names, columns=[lambda col: col]),\n", - " layout=weave.legacy.panels.BoardPanelLayout(x=0, y=12, w=12, h=6)\n", - " ),\n", - " weave.legacy.panels.BoardPanel(\n", - " lambda row_names: weave.legacy.panels.Table(row_names, columns=[lambda row: row]),\n", - " layout=weave.legacy.panels.BoardPanelLayout(x=12, y=12, w=12, h=6)\n", - " ),\n", - " ]\n", - ")\n", - "\"\"\", 'stable_diffusion_board_str')" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/experimental/skip_test/Diffusion story.ipynb b/examples/experimental/skip_test/Diffusion story.ipynb deleted file mode 100644 index 8a9f8b6771d..00000000000 --- a/examples/experimental/skip_test/Diffusion story.ipynb +++ /dev/null @@ -1,122 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "00411857", - "metadata": {}, - "outputs": [], - "source": [ - "import weave\n", - "from weave import weave_internal\n", - "from weave.legacy.ecosystem.all import langchain\n", - "weave.use_frontend_devmode()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "30c46465", - "metadata": {}, - "outputs": [], - "source": [ - "weave.legacy.panels.Board(\n", - " vars={\n", - " 'gpt35': langchain.chat_openai(\"gpt-3.5-turbo\", 0.7),\n", - " 'gpt4': langchain.chat_openai(\"gpt-4\", 0.7),\n", - " 'chat_model': lambda gpt4, gpt35: gpt4,\n", - " 'director': 'Wes Anderson',\n", - " 'director_emotion': 'worldy',\n", - " 'film_style': 'rollerblading documentary',\n", - " 'number_of_panes': 4,\n", - " 'story_gen_prompt': \"\"\"\n", - " You are {director} creating the next {film_style} film. Today you're feeling {director_emotion}.\n", - " In initial sketches, you write a {number_of_panes} step sketch of the plot of your film.\n", - " You include specific details about how the plot proceeds.\n", - " What do you write? Since you are so creative, you can overcome any limitations you might\n", - " think you have when it comes to writing style. In this case, you write your outline as a json list\n", - " with one item for each step.\n", - " \"\"\",\n", - " 'prompt': lambda director, director_emotion, film_style, number_of_panes, story_gen_prompt: story_gen_prompt.format(weave.legacy.ops.dict_(\n", - " director= director,\n", - " director_emotion=director_emotion,\n", - " film_style=film_style,\n", - " number_of_panes=number_of_panes)),\n", - " 'storyline': lambda prompt, chat_model: chat_model.predict(prompt),\n", - " 'storyline_parsed': lambda storyline: storyline.json_parse_list(),\n", - " 'panel_prompt': 'A storyboard pane for a movie by {director} for their next {film_style} film, The scene description is: {panel}',\n", - " 'storyboard_panes': lambda storyline_parsed, panel_prompt, director, film_style:\n", - " storyline_parsed.map(lambda row:\n", - " panel_prompt.format(weave.legacy.ops.dict_(\n", - " panel=row.json_dumps(),\n", - " director=director,\n", - " film_style=film_style,\n", - " ))).stable_diffusion()\n", - " },\n", - " panels=[ \n", - " weave.legacy.panels.BoardPanel(\n", - " lambda storyline_parsed: storyline_parsed,\n", - " layout=weave.legacy.panels.BoardPanelLayout(x=0, y=0, w=24, h=6)\n", - " ),\n", - " weave.legacy.panels.BoardPanel(\n", - " lambda storyboard_panes: weave.legacy.panels.Each(storyboard_panes),\n", - " layout=weave.legacy.panels.BoardPanelLayout(x=0, y=0, w=24, h=12)\n", - " )\n", - " ]\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c8d3d23b", - "metadata": {}, - "outputs": [], - "source": [ - "# imperative version\n", - "\n", - "gpt35 = langchain.chat_openai(\"gpt-3.5-turbo\", 0.7)\n", - "gpt4 = langchain.chat_openai(\"gpt-4\", 0.7)\n", - "chat_model = gpt4\n", - "\n", - "director = 'Wes Anderson'\n", - "film_style = 'great american'\n", - "number_of_panes = 4\n", - "story_gen_prompt = weave.save(\"\"\"\n", - "You are {director} creating the next {film_style} film.\n", - "In initial sketches, you write a {number_of_panes} step sketch of the plot of your film.\n", - "You include specific details about how the plot proceeds.\n", - "What do you write? Since you are so creative, you can overcome any limitations you might\n", - "think you have when it comes to writing style. In this case, you write your outline as a json list\n", - "with one item for each step.\n", - "\"\"\",\n", - " name='story_gen_prompt')\n", - "storyline = gpt35.predict(story_gen_prompt.format({\n", - " 'director': director,\n", - " 'film_style': film_style,\n", - " 'number_of_panes': number_of_panes}))\n", - "storyline_parsed = storyline.json_parse()\n", - "#storyline_parsed" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "6fddc9a8", - "metadata": {}, - "outputs": [], - "source": [ - "#weave.legacy.panels.Each(storyline_parsed.map(lambda row: row.json_dumps()).stable_diffusion())" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/experimental/skip_test/Docbot.ipynb b/examples/experimental/skip_test/Docbot.ipynb deleted file mode 100644 index 328117dfa9a..00000000000 --- a/examples/experimental/skip_test/Docbot.ipynb +++ /dev/null @@ -1,329 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "ef0c98ad", - "metadata": {}, - "outputs": [], - "source": [ - "import pathlib\n", - "import weave\n", - "weave.use_frontend_devmode()\n", - "from weave.legacy.ecosystem import langchain" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8f7192c3", - "metadata": {}, - "outputs": [], - "source": [ - "neg_feedback = [\"Is there a way that I can embed an iframe component in Weights and Biases report?\",\n", - " \"how do i create the pretty sweeps plot?\",\n", - " \"I'm getting error 400. What can I do?\",\n", - " \"How can I move a column in a table to the right?\",\n", - " \"In Prompts, how can I resize the Trace Timeline to make it bigger or full screen?\",\n", - " \"I have a question about exporting CSV files from a web panel. Whenever I do this, I always get two extra columns for MAX and MIN values, even if I only have one data curve. Does anyone know how to solve this issue?\",\n", - " \"artifacts cli command to upload a folder of images\",\n", - " \"Is there a recommended way to use Launch in an SLURM environment?\",\n", - " \"How to export a single chart's data using the API?\",\n", - " \"how can i login with a different wandb user?\"\n", - "]\n", - "\n", - "pos_feedback = [\"my logging doesn't seem to include errors when the training crashes, how do I change the logging level for wandb logging?\",\n", - " \"how can i get the data from my wandb run by querying my logs using python?\",\n", - " \"how do I fix an error with wandb Table construction from pandas dataframe: TypeError: Data row contained incompatible types\",\n", - " \"how can i make a heatmap using vega and plot it to wandb? what is the vegaspec?\",\n", - " \"is there a good way to join 2 tables together programmatically?\",\n", - " \"I have a question about sweeps. How can you constrain relationship between parameters. For example, I now that if num_layers * hidden_dim is large, I'll run out of GPU memory. So, also I would like to explore some hyperparameter space, there are some combination I know will fail. optuna as a way to do that: you can throw an special exception to cancel a run during a sweep, so that it is not recorded. Is there something similar in W&B, or another way of pruning unwanted combination of hyperparameters?\",\n", - " \"where can I find my run_id\",\n", - " \"How do I group runs?\",\n", - " \"I am using the Hugging Face trainer to train a GPT-2 model. How can I log in wandb the results of the model in each evaluation?\",\n", - " \"I am looking to finetune LLAMA on my own dataset using OpenAI, can you give me examples on how to do this?\"\n", - " ]\n", - "\n", - "all_qs = [\n", - " {'question': q, 'feedback': 'positive'} for q in pos_feedback] + [\n", - " {'question': q, 'feedback': 'negative'} for q in neg_feedback]\n", - "\n", - "questions = weave.save(all_qs, 'eval_questions')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "03a68bf9", - "metadata": {}, - "outputs": [], - "source": [ - "questions" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f6e1c366", - "metadata": {}, - "outputs": [], - "source": [ - "from langchain.docstore.document import Document\n", - "from langchain.text_splitter import (\n", - " MarkdownTextSplitter,\n", - " PythonCodeTextSplitter,\n", - " TokenTextSplitter,\n", - ")\n", - "# Get markdown files from our docs repo\n", - "\n", - "# Checkout of our docs repo: https://github.com/wandb/docodile/\n", - "DOC_DIR = '/Users/shawn/code2/docodile'\n", - "DOC_SUFFIX = '.md'\n", - "\n", - "docs = []\n", - "for file in pathlib.Path(DOC_DIR).glob('**/*' + DOC_SUFFIX):\n", - " with file.open('r') as f:\n", - " # store them as langchain Document objects\n", - " docs.append(Document(page_content=f.read(), metadata={'path': file.name}))\n", - "docs = MarkdownTextSplitter().split_documents(docs)\n", - "docs = TokenTextSplitter().split_documents(docs)\n", - "\n", - "docs = weave.save(docs, 'wandb-docs')\n", - "docs" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "624e0b9e", - "metadata": {}, - "outputs": [], - "source": [ - "# Vector store with langchain\n", - "from langchain.vectorstores import VectorStore, FAISS\n", - "from langchain.embeddings.openai import OpenAIEmbeddings\n", - "embeddings = OpenAIEmbeddings()\n", - "vector_store = FAISS.from_documents(weave.use(docs), embeddings)\n", - "\n", - "weave.save(vector_store)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a97a3a9c", - "metadata": {}, - "outputs": [], - "source": [ - "# Vector store with Weave\n", - "\n", - "# Stuff you can do:\n", - "# - .similarity_search()\n", - "# - but currently there is a crash because Row.Group tries to render and fails. Need to switch the\n", - "# panel to Table and then change column to row.__getattr__('page_content')\n", - "# - .document_embeddings\n", - "# - this gets the embeddings out of FAISS, and also performs FAISS' k-means with 20 clusters\n", - "# - switch to projection.plot\n", - "# TODO:\n", - "# - give control over k for k-means\n", - "\n", - "from weave.legacy.ecosystem import langchain\n", - "\n", - "vector_store_node = langchain.faiss_from_documents(docs, langchain.openai_embeddings())\n", - "vector_store_node" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "bd31e988", - "metadata": {}, - "outputs": [], - "source": [ - "# With langchain\n", - "from langchain.chat_models import ChatOpenAI\n", - "from langchain.chains import RetrievalQA\n", - "model_gpt_35_temp07 = RetrievalQA.from_chain_type(\n", - " llm=ChatOpenAI(model_name='gpt-3.5-turbo', temperature=0.7),\n", - " chain_type='stuff',\n", - " retriever=vector_store.as_retriever()\n", - " )\n", - "model = model_gpt_35_temp07\n", - "model = weave.save(model_gpt_35_temp07, 'mymodel')\n", - "\n", - "#model.run('hello')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "694ce5d6", - "metadata": {}, - "outputs": [], - "source": [ - "# With weave\n", - "from weave.legacy.ecosystem import langchain\n", - "qa = langchain.retrieval_qa_from_chain_type(\n", - " langchain.chat_openai('gpt-3.5-turbo', 0.7),\n", - " 'stuff',\n", - " vector_store_node)\n", - "qa.run('hello')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "27fc1618", - "metadata": {}, - "outputs": [], - "source": [ - "# from langchain.chat_models import ChatOpenAI\n", - "# from langchain.chains import RetrievalQA\n", - "model_02 = RetrievalQA.from_chain_type(\n", - " llm=ChatOpenAI(model_name='gpt-3.5-turbo', temperature=0.2),\n", - " chain_type='stuff',\n", - " retriever=vector_store.as_retriever()\n", - " )\n", - "model_07 = RetrievalQA.from_chain_type(\n", - " llm=ChatOpenAI(model_name='gpt-3.5-turbo', temperature=0.7),\n", - " chain_type='stuff',\n", - " retriever=vector_store.as_retriever()\n", - " )" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f18ae43f", - "metadata": {}, - "outputs": [], - "source": [ - "# Two with LC and weave.save\n", - "\n", - "models = [model_02, model_07]\n", - "models = weave.save(models, 'docbot-models')\n", - "models.run('hello')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "34c3e345", - "metadata": {}, - "outputs": [], - "source": [ - "# You can map over questions\n", - "weave.legacy.panels.Table(questions.limit(3), columns=[\n", - " lambda q: q['question'],\n", - " lambda q: models.run(q['question'])])" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "876bc738", - "metadata": {}, - "outputs": [], - "source": [ - "# Interactive evaluation and exploration dashboard\n", - "\n", - "weave.legacy.panels.Board(\n", - " vars={\n", - " 'documents': docs,\n", - " 'questions': questions.limit(2),\n", - " 'embeddings': langchain.openai_embeddings(),\n", - " 'vector_store': lambda embeddings, documents: langchain.faiss_from_documents(documents, embeddings),\n", - " 'doc_embeddings': lambda vector_store: vector_store.document_embeddings(),\n", - " #'models': models,\n", - " 'model_a': lambda vector_store: langchain.retrieval_qa_from_chain_type(\n", - " langchain.chat_openai('gpt-3.5-turbo', 0.2),\n", - " 'stuff',\n", - " vector_store),\n", - " 'model_b': lambda vector_store: langchain.retrieval_qa_from_chain_type(\n", - " langchain.chat_openai('gpt-3.5-turbo', 0.7),\n", - " 'stuff',\n", - " vector_store),\n", - " 'models': lambda model_a, model_b: weave.legacy.ops.make_list(a=model_a, b=model_b),\n", - " 'projection': lambda doc_embeddings: doc_embeddings.projection2D(\n", - " 'pca',\n", - " 'single',\n", - " ['embedding'],\n", - " {'pca': {},\n", - " 'tsne': {\n", - " 'perplexity': 30,\n", - " 'learningRate': 10,\n", - " 'iterations': 25\n", - " },\n", - " 'umap': {\n", - " 'neighbors': 15,\n", - " 'minDist': 0.1,\n", - " 'spread': 1.0\n", - " }\n", - " }),\n", - " },\n", - " panels=[ \n", - " weave.legacy.panels.BoardPanel(\n", - " lambda models: weave.legacy.panels.Each(models.run(\"What is Weave?\")),\n", - " layout=weave.legacy.panels.BoardPanelLayout(x=0, y=0, w=24, h=6)\n", - " ),\n", - " weave.legacy.panels.BoardPanel(\n", - " lambda model_a, model_b: weave.legacy.panels.Table(questions,\n", - " columns=[\n", - " lambda question: question['question'],\n", - " lambda question: question['feedback'],\n", - " weave.legacy.panels.TableColumn(\n", - " lambda question: model_a.run(question['question']).result,\n", - " name='model_a'\n", - " ),\n", - " weave.legacy.panels.TableColumn(\n", - " lambda question: model_b.run(question['question']).result,\n", - " name='model_b'\n", - " ),\n", - " ]),\n", - " layout=weave.legacy.panels.BoardPanelLayout(x=0, y=6, w=24, h=6)\n", - " ), \n", - " weave.legacy.panels.BoardPanel(\n", - " id='docs_projection',\n", - " panel=lambda projection: weave.legacy.panels.Plot(\n", - " projection,\n", - " x=lambda row: row['projection.x'],\n", - " y=lambda row: row['projection.y'],\n", - " color=lambda row: row['source.cluster']\n", - " ),\n", - " layout=weave.legacy.panels.BoardPanelLayout(x=0, y=12, w=12, h=6)\n", - " ),\n", - " weave.legacy.panels.BoardPanel(\n", - " lambda docs_projection: docs_projection.selected_data(),\n", - " layout=weave.legacy.panels.BoardPanelLayout(x=12, y=12, w=12, h=6)\n", - " ),\n", - " weave.legacy.panels.BoardPanel(\n", - " lambda documents: weave.legacy.panels.Table(documents,\n", - " columns=[\n", - " lambda doc: doc.page_content,\n", - " lambda doc: doc.metadata['path']\n", - " ]),\n", - " layout=weave.legacy.panels.BoardPanelLayout(x=0, y=18, w=12, h=6)\n", - " ),\n", - " weave.legacy.panels.BoardPanel(\n", - " lambda vector_store: weave.legacy.panels.Table(vector_store.similarity_search('weave'),\n", - " columns=[\n", - " lambda doc: doc.page_content,\n", - " lambda doc: doc.metadata['path'] \n", - " ]),\n", - " layout=weave.legacy.panels.BoardPanelLayout(x=12, y=18, w=12, h=6)\n", - " ),\n", - " ]\n", - ")" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/experimental/skip_test/Hack demo.ipynb b/examples/experimental/skip_test/Hack demo.ipynb deleted file mode 100644 index 8df75d8efcc..00000000000 --- a/examples/experimental/skip_test/Hack demo.ipynb +++ /dev/null @@ -1,117 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "6e89befe", - "metadata": {}, - "outputs": [], - "source": [ - "import typing\n", - "import weave\n", - "from weave.legacy.ecosystem import hackdemo\n", - "\n", - "PROJECT = 'hackdemo'" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "e0209514", - "metadata": {}, - "outputs": [], - "source": [ - "dataset = hackdemo.ca_housing_dataset(25000)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "4893a4a8", - "metadata": {}, - "outputs": [], - "source": [ - "weave.show(dataset)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "35d52005", - "metadata": {}, - "outputs": [], - "source": [ - "@weave.op()\n", - "def table_summary(table: typing.Any) -> list[weave.legacy.panels.Panel]:\n", - " if not table:\n", - " return\n", - " col_names = list(table[0].keys())\n", - " with weave.lazy_execution():\n", - " cols = {col_name: table.pick(col_name) for col_name in col_names}\n", - " panels = []\n", - " for col_name, col_values in cols.items():\n", - " panels.append(weave.legacy.panels.LabeledItem(item=col_values, label=col_name))\n", - " return panels" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "42638af4", - "metadata": {}, - "outputs": [], - "source": [ - "data_card = hackdemo.Dataset(\n", - " name='California Housing',\n", - " created_by=hackdemo.User(name='Pace, R. Kelley'),\n", - " description='S&P Letters Data\\n\\nWe collected information on the variables using all the block groups in California from the 1990 Cens us. In this sample a block group on average includes 1425.5 individuals living in a geographically co mpact area. Naturally, the geographical area included varies inversely with the population density. W e computed distances among the centroids of each block group as measured in latitude and longitude. W e excluded all the block groups reporting zero entries for the independent and dependent variables. T he final data contained 20,640 observations on 9 variables. The dependent variable is ln(median house value).',\n", - " updated_at='August 25th 2021, at 3:36:29pm',\n", - " table=weave.use(dataset) \n", - ")\n", - "\n", - "saved_data_card = weave.save(data_card)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "578adec4", - "metadata": {}, - "outputs": [], - "source": [ - "#weave.publish(data_card, '%s/ca-housing-cadr' % PROJECT)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c35a4702", - "metadata": {}, - "outputs": [], - "source": [ - "hackdemo.ca_housing_dataset_card(saved_data_card)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/experimental/skip_test/KerasModel.ipynb b/examples/experimental/skip_test/KerasModel.ipynb deleted file mode 100644 index 86de437c093..00000000000 --- a/examples/experimental/skip_test/KerasModel.ipynb +++ /dev/null @@ -1,305 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "ad4e3073", - "metadata": {}, - "outputs": [], - "source": [ - "import weave\n", - "weave.use_frontend_devmode()\n", - "from weave.legacy.ecosystem import keras as weave_keras" - ] - }, - { - "cell_type": "markdown", - "id": "2f3cbb0d", - "metadata": {}, - "source": [ - "# Train Language Model\n", - "\n", - "From: https://keras.io/examples/nlp/text_classification_from_scratch/" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ec47e09b", - "metadata": {}, - "outputs": [], - "source": [ - "import tensorflow as tf\n", - "import numpy as np" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d288f1b0", - "metadata": {}, - "outputs": [], - "source": [ - "!curl -O https://ai.stanford.edu/~amaas/data/sentiment/aclImdb_v1.tar.gz\n", - "!tar -xf aclImdb_v1.tar.gz\n", - "!rm -r aclImdb/train/unsup" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "4e74e162", - "metadata": {}, - "outputs": [], - "source": [ - "batch_size = 32\n", - "raw_train_ds = tf.keras.preprocessing.text_dataset_from_directory(\n", - " \"aclImdb/train\",\n", - " batch_size=batch_size,\n", - " validation_split=0.2,\n", - " subset=\"training\",\n", - " seed=1337,\n", - ")\n", - "raw_val_ds = tf.keras.preprocessing.text_dataset_from_directory(\n", - " \"aclImdb/train\",\n", - " batch_size=batch_size,\n", - " validation_split=0.2,\n", - " subset=\"validation\",\n", - " seed=1337,\n", - ")\n", - "raw_test_ds = tf.keras.preprocessing.text_dataset_from_directory(\n", - " \"aclImdb/test\", batch_size=batch_size\n", - ")\n", - "\n", - "print(f\"Number of batches in raw_train_ds: {raw_train_ds.cardinality()}\")\n", - "print(f\"Number of batches in raw_val_ds: {raw_val_ds.cardinality()}\")\n", - "print(f\"Number of batches in raw_test_ds: {raw_test_ds.cardinality()}\")\n", - "\n", - "from tensorflow.keras.layers import TextVectorization\n", - "import string\n", - "import re\n", - "\n", - "# Having looked at our data above, we see that the raw text contains HTML break\n", - "# tags of the form '
'. These tags will not be removed by the default\n", - "# standardizer (which doesn't strip HTML). Because of this, we will need to\n", - "# create a custom standardization function.\n", - "def custom_standardization(input_data):\n", - " lowercase = tf.strings.lower(input_data)\n", - " stripped_html = tf.strings.regex_replace(lowercase, \"
\", \" \")\n", - " return tf.strings.regex_replace(\n", - " stripped_html, f\"[{re.escape(string.punctuation)}]\", \"\"\n", - " )\n", - "\n", - "\n", - "# Model constants.\n", - "max_features = 20000\n", - "embedding_dim = 128\n", - "sequence_length = 500\n", - "\n", - "# Now that we have our custom standardization, we can instantiate our text\n", - "# vectorization layer. We are using this layer to normalize, split, and map\n", - "# strings to integers, so we set our 'output_mode' to 'int'.\n", - "# Note that we're using the default split function,\n", - "# and the custom standardization defined above.\n", - "# We also set an explicit maximum sequence length, since the CNNs later in our\n", - "# model won't support ragged sequences.\n", - "vectorize_layer = TextVectorization(\n", - " standardize=custom_standardization,\n", - " max_tokens=max_features,\n", - " output_mode=\"int\",\n", - " output_sequence_length=sequence_length,\n", - ")\n", - "\n", - "vectorize_layer_non_custom = TextVectorization(\n", - "# standardize=custom_standardization,\n", - " max_tokens=max_features,\n", - " output_mode=\"int\",\n", - " output_sequence_length=sequence_length,\n", - ")\n", - "\n", - "# Now that the vocab layer has been created, call `adapt` on a text-only\n", - "# dataset to create the vocabulary. You don't have to batch, but for very large\n", - "# datasets this means you're not keeping spare copies of the dataset in memory.\n", - "\n", - "# Let's make a text-only dataset (no labels):\n", - "text_ds = raw_train_ds.map(lambda x, y: x)\n", - "# Let's call `adapt`:\n", - "vectorize_layer.adapt(text_ds)\n", - "vectorize_layer_non_custom.adapt(text_ds)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "7e48486a", - "metadata": {}, - "outputs": [], - "source": [ - "def vectorize_text(text, label):\n", - " text = tf.expand_dims(text, -1)\n", - " return vectorize_layer(text), label\n", - "\n", - "\n", - "# Vectorize the data.\n", - "train_ds = raw_train_ds.map(vectorize_text)\n", - "val_ds = raw_val_ds.map(vectorize_text)\n", - "test_ds = raw_test_ds.map(vectorize_text)\n", - "\n", - "# Do async prefetching / buffering of the data for best performance on GPU.\n", - "train_ds = train_ds.cache().prefetch(buffer_size=10)\n", - "val_ds = val_ds.cache().prefetch(buffer_size=10)\n", - "test_ds = test_ds.cache().prefetch(buffer_size=10)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "7f053ba5", - "metadata": {}, - "outputs": [], - "source": [ - "from tensorflow.keras import layers\n", - "\n", - "# A integer input for vocab indices.\n", - "inputs = tf.keras.Input(shape=(None,), dtype=\"int64\")\n", - "\n", - "# Next, we add a layer to map those vocab indices into a space of dimensionality\n", - "# 'embedding_dim'.\n", - "x = layers.Embedding(max_features, embedding_dim)(inputs)\n", - "x = layers.Dropout(0.5)(x)\n", - "\n", - "# Conv1D + global max pooling\n", - "x = layers.Conv1D(128, 7, padding=\"valid\", activation=\"relu\", strides=3)(x)\n", - "x = layers.Conv1D(128, 7, padding=\"valid\", activation=\"relu\", strides=3)(x)\n", - "x = layers.GlobalMaxPooling1D()(x)\n", - "\n", - "# We add a vanilla hidden layer:\n", - "x = layers.Dense(128, activation=\"relu\")(x)\n", - "x = layers.Dropout(0.5)(x)\n", - "\n", - "# We project onto a single unit output layer, and squash it with a sigmoid:\n", - "predictions = layers.Dense(1, activation=\"sigmoid\", name=\"predictions\")(x)\n", - "\n", - "text_model = tf.keras.Model(inputs, predictions)\n", - "\n", - "# Compile the model with binary crossentropy loss and an adam optimizer.\n", - "text_model.compile(loss=\"binary_crossentropy\", optimizer=\"adam\", metrics=[\"accuracy\"])" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f177875d", - "metadata": {}, - "outputs": [], - "source": [ - "epochs = 1\n", - "\n", - "# Fit the model using the train and test datasets.\n", - "text_model.fit(train_ds, validation_data=val_ds, epochs=epochs)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a276fc85", - "metadata": {}, - "outputs": [], - "source": [ - "# A string input\n", - "inputs = tf.keras.Input(shape=(1,), dtype=\"string\")\n", - "# Turn strings into vocab indices\n", - "indices = vectorize_layer_non_custom(inputs)\n", - "# Turn vocab indices into predictions\n", - "outputs = text_model(indices)\n", - "\n", - "# Our end to end model\n", - "end_to_end_model = tf.keras.Model(inputs, outputs)\n", - "end_to_end_model.compile(\n", - " loss=\"binary_crossentropy\", optimizer=\"adam\", metrics=[\"accuracy\"]\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "06493feb", - "metadata": {}, - "outputs": [], - "source": [ - "saved_text_model = weave.save(end_to_end_model)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3040ac71", - "metadata": {}, - "outputs": [], - "source": [ - "weave_keras.call_string(saved_text_model, \"single test string\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "027e8660", - "metadata": {}, - "outputs": [], - "source": [ - "weave.legacy.panels.Table([\n", - " {'input_str': \"This is a test\"},\n", - " {'input_str': \"This is a horrible test\"},\n", - " {'input_str': \"I love weave\"},\n", - " {'input_str': \"please work!\"},\n", - " {'input_str': \"I am a person in a house\"},\n", - "], columns=[\n", - " lambda row: row['input_str'],\n", - " lambda row: weave_keras.call_string(saved_text_model, row['input_str'])\n", - "])" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f0e435bc", - "metadata": {}, - "outputs": [], - "source": [ - "# published_text_model = weave.save(end_to_end_model)\n", - "published_text_model = weave.publish(end_to_end_model)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8691e38a", - "metadata": {}, - "outputs": [], - "source": [ - "weave_keras.call_string(published_text_model, \"single test string\")" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/experimental/skip_test/LLMs Via Weave.ipynb b/examples/experimental/skip_test/LLMs Via Weave.ipynb deleted file mode 100644 index cf781c31cce..00000000000 --- a/examples/experimental/skip_test/LLMs Via Weave.ipynb +++ /dev/null @@ -1,111 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "2dc04fd0", - "metadata": {}, - "outputs": [], - "source": [ - "import weave\n", - "# weave.use_frontend_devmode()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "615e5163", - "metadata": {}, - "outputs": [], - "source": [ - "import weave\n", - "import weave.legacy\n", - "panels = weave.legacy.panels\n", - "\n", - "project = \"tim-test-llms-test-all-final-colab-01\"\n", - "entity = \"timssweeney\"\n", - "\n", - "# Create a Weave Query to get Traces\n", - "traces = (\n", - " weave.legacy.ops\n", - " .project(entity, project)\n", - " .runs()\n", - " .history().map(\n", - " lambda row: row['langchain_trace'].dropna()\n", - " )\n", - " .concat()\n", - " .dropna()\n", - " .sort(\n", - " lambda row: weave.legacy.ops.make_list(a=row.startTime()), \n", - " ['asc']\n", - " )\n", - ")\n", - "\n", - "# Create a table from the query\n", - "table = panels.Table(traces)\n", - "table_state = table.config.tableState\n", - "\n", - "# Add columns, set sorting, and specify panels for columns\n", - "table_state.add_column(lambda row: row.traceSummaryDict()[\"isSuccess\"], \"Success\")\n", - "start_time_col_id = table_state.add_column(lambda row: row.traceSummaryDict()[\"startTime\"].toTimestamp(), \"Timestamp\")\n", - "table_state.enable_sort(start_time_col_id, \"desc\")\n", - "\n", - "markdown_panel = panels.table_state.PanelDef('string', {'mode': 'markdown'})\n", - "table_state.add_column(lambda row: row.traceSummaryDict()[\"formattedInput\"], \"Input\", markdown_panel)\n", - "table_state.add_column(lambda row: row.traceSummaryDict()[\"formattedOutput\"], \"Output\", markdown_panel)\n", - "table_state.add_column(lambda row: row.traceSummaryDict()[\"formattedChain\"], \"Chain\", markdown_panel)\n", - "table_state.add_column(lambda row: row.traceSummaryDict()[\"error\"], \"Error\")\n", - "table_state.add_column(lambda row: row.traceSummaryDict()[\"modelHash\"], \"Model ID\")\n", - "\n", - "# Compose into a group with detail view below\n", - "dash = panels.Group(\n", - " items={\n", - " \"all_traces\": table,\n", - " \"selected_details\": lambda all_traces: panels.Card(\n", - " title='Trace Viewer',\n", - " subtitle='',\n", - " content=[\n", - " panels.CardTab(\n", - " name='Trace Timeline',\n", - " content=panels.PanelWBTraceTreeTraceViewer(all_traces.active_data())\n", - " ),\n", - " panels.CardTab(\n", - " name='Model Architecture',\n", - " content=panels.PanelWBTraceTreeModelViewer(all_traces.active_data())\n", - " ),\n", - "\n", - " ]\n", - " )\n", - " }\n", - ")\n", - "\n", - "# Publish to W&B\n", - "# weave.publish(dash, \"weave_ops/prompts_in_weave1\")\n", - "\n", - "# View locally\n", - "dash" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/experimental/skip_test/Model Cards.ipynb b/examples/experimental/skip_test/Model Cards.ipynb deleted file mode 100644 index f00044eebfd..00000000000 --- a/examples/experimental/skip_test/Model Cards.ipynb +++ /dev/null @@ -1,79 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "c6b2e19b", - "metadata": {}, - "outputs": [], - "source": [ - "import weave\n", - "weave.use_frontend_devmode()\n", - "from weave.legacy.ecosystem import models" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "4b51e4d5", - "metadata": {}, - "outputs": [], - "source": [ - "# Declare an instance of a ModelCard\n", - "model_card = models.ModelCard(\n", - " model_name='GPT-3',\n", - " created_by=models.User(name='OpenAI'),\n", - " updated_at='August 25th 2021, at 3:36:29pm', # TODO: timestamp\n", - " model_type='Language model',\n", - " primary_metric=models.TargetMetric(name='acc', direction='up'),\n", - " application='Our API has been deployed in thousands of applications with tasks ranging from helping people learn new languages to solving complex classification problems',\n", - " example='...Slightly better example...'\n", - ")\n", - "model_card_ref = weave.save(model_card, 'my-model-card')\n", - "models.model_card_panel(model_card_ref)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ba518a7e", - "metadata": {}, - "outputs": [], - "source": [ - "# Publish it to W&B\n", - "model_card_wbref = weave.publish(model_card, name='my-model')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "32dd7d50", - "metadata": {}, - "outputs": [], - "source": [ - "weave.show(model_card_wbref)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/experimental/skip_test/Models.ipynb b/examples/experimental/skip_test/Models.ipynb deleted file mode 100644 index d54666776e4..00000000000 --- a/examples/experimental/skip_test/Models.ipynb +++ /dev/null @@ -1,103 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "0c235474", - "metadata": {}, - "outputs": [], - "source": [ - "import weave\n", - "import xgboost\n", - "from weave.legacy.ecosystem import shap\n", - "#weave.use_frontend_devmode()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "63a29a5f", - "metadata": {}, - "outputs": [], - "source": [ - "dataset = weave.use(shap.ca_housing_dataset(1))\n", - "dataset_arrow = weave.legacy.ops.dataframe_to_arrow(dataset)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c5633045", - "metadata": {}, - "outputs": [], - "source": [ - "weave.show(dataset_arrow)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b6edbb55", - "metadata": {}, - "outputs": [], - "source": [ - "weave.publish(dataset_arrow, 'my-dataset-transform')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "dcccca4b", - "metadata": {}, - "outputs": [], - "source": [ - "weave.show(x)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "40f37c5a", - "metadata": {}, - "outputs": [], - "source": [ - "weave.legacy.ops.get('wandb-artifact://shawn/weave_ops/my-dataset-transform:8c478776a5e01c3c051d')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "7a0d6be5", - "metadata": {}, - "outputs": [], - "source": [ - "@weave.op()\n", - "def some_plotly_op():\n", - " return Plotly.plot(whatever)\n", - "\n", - "weave.name(some_plotly_op(), name='my-cool-plot')" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/experimental/skip_test/Monitor2.ipynb b/examples/experimental/skip_test/Monitor2.ipynb deleted file mode 100644 index 27fad384dec..00000000000 --- a/examples/experimental/skip_test/Monitor2.ipynb +++ /dev/null @@ -1,310 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "039f298a", - "metadata": {}, - "outputs": [], - "source": [ - "import pandas as pd\n", - "import weave\n", - "weave.use_frontend_devmode()\n", - "from weave.legacy.ops_arrow.list_ import dataframe_to_arrow\n", - "from weave.legacy.ecosystem import wandb" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5e0d6245", - "metadata": {}, - "outputs": [], - "source": [ - "import numpy as np\n", - "import pandas as pd\n", - "import random\n", - "from faker import Faker\n", - "from datetime import timedelta, time\n", - "\n", - "# Generate the version schedule\n", - "def generate_version_schedule(start_date, end_date):\n", - " current_date = start_date\n", - " versions = ['1.0']\n", - " version_schedule = {}\n", - " while current_date <= end_date:\n", - " date_versions = []\n", - " for version in versions:\n", - " service_percent = random.uniform(0, 1)\n", - " date_versions.append((version, service_percent))\n", - "\n", - " version_schedule[current_date.date()] = date_versions\n", - " current_date += timedelta(days=1)\n", - " if random.random() < 0.10: # 5% chance to introduce a new version each day\n", - " new_version = f'{float(versions[-1])+0.1:.1f}'\n", - " versions.append(new_version)\n", - " if len(versions) > 1 and random.random() < 0.10:\n", - " versions.pop(0)\n", - " return version_schedule\n", - "\n", - "# Generate the latency schedule\n", - "def generate_latency_schedule(start_date, end_date):\n", - " latency_schedule = {}\n", - " for current_date in pd.date_range(start_date, end_date):\n", - " base_latency = random.uniform(0.1, 1)\n", - " day_factor = random.uniform(0.5, 1.5)\n", - " month_factor = random.uniform(0.5, 1.5)\n", - " latency = base_latency * day_factor * month_factor\n", - " latency_schedule[current_date.date()] = latency\n", - " return latency_schedule\n", - "\n", - "# Generate the cost schedule\n", - "def generate_cost_schedule(start_date, end_date, cost_change_date):\n", - " cost_schedule = {}\n", - " current_date = start_date\n", - " cost_per_token = 0.01\n", - " while current_date <= end_date:\n", - " if current_date >= cost_change_date:\n", - " cost_per_token = 0.005\n", - " cost_schedule[current_date.date()] = cost_per_token\n", - " current_date += timedelta(days=1)\n", - " return cost_schedule\n", - "\n", - "def generate_user_usage_schedule(start_date, end_date, users):\n", - " user_usage_schedule = []\n", - " for user in users:\n", - " current_date = start_date + timedelta(days=random.randrange(90))\n", - " while current_date <= end_date:\n", - " usage_periods = random.randint(1, 30)\n", - " for _ in range(usage_periods):\n", - " period_length_timedelta = timedelta(hours=random.randint(1, 24 * 7))\n", - " rate = random.uniform(0.1, 10)\n", - " user_usage_schedule.append((current_date, user, period_length_timedelta, rate))\n", - " current_date += period_length_timedelta # Increment current_date\n", - " if current_date > end_date:\n", - " break\n", - " return user_usage_schedule" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9d6a0318", - "metadata": {}, - "outputs": [], - "source": [ - "# Define our fake users\n", - "fake = Faker()\n", - "users = [fake.user_name() for _ in range(100)]\n", - "\n", - "# Read the file and generate prompts\n", - "with open('/Users/shawn/Downloads/t8.shakespeare.txt', 'r') as f:\n", - " lines = f.read().split('\\n')\n", - "\n", - "# Define the time range\n", - "start_date = pd.to_datetime('2023-01-01', utc=True)\n", - "end_date = pd.to_datetime('2023-03-31', utc=True)\n", - "cost_change_date = pd.to_datetime('2023-02-15', utc=True)\n", - "\n", - "# Generate the schedules\n", - "version_schedule = generate_version_schedule(start_date, end_date)\n", - "latency_schedule = generate_latency_schedule(start_date, end_date)\n", - "cost_schedule = generate_cost_schedule(start_date, end_date, cost_change_date)\n", - "user_usage_schedule = generate_user_usage_schedule(start_date, end_date, users)\n", - "\n", - "# Helper function to generate a random completion\n", - "def generate_completion(prompt):\n", - " words = prompt.split()\n", - " completion = ' '.join(random.choices(words, k=int(len(words)* (random.random() + 0.1) * 10)))\n", - " return completion\n", - "\n", - "#latency_schedule" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a672c782", - "metadata": {}, - "outputs": [], - "source": [ - "import tqdm\n", - "data = []\n", - "for usage in tqdm.tqdm(user_usage_schedule):\n", - " usage_date, user, usage_period, rate = usage\n", - " \n", - " end_date = usage_date + usage_period\n", - " increment = timedelta(hours=rate)\n", - " \n", - " while usage_date < end_date:\n", - " # Find the version that was active during this usage\n", - " if usage_date.date() not in version_schedule:\n", - " break\n", - " active_versions = version_schedule[usage_date.date()]\n", - " #active_versions = [(version, percent) for date, version, percent in version_schedule if date.date() == usage_date.date()]\n", - " # Normalize the service percentages\n", - " total_percent = sum([percent for version, percent in active_versions])\n", - " if total_percent == 0:\n", - " continue\n", - " normalized_percentages = [percent / total_percent for version, percent in active_versions]\n", - "\n", - " version = np.random.choice([v for v, p in active_versions], p=normalized_percentages)\n", - " \n", - " # Find the cost during this usage\n", - " cost_per_token = cost_schedule[usage_date.date()]\n", - " \n", - " # Find the average latency during this usage\n", - " latency = latency_schedule[usage_date.date()]\n", - " latency *= (0.9 + random.random() *.2)\n", - " \n", - " prompt = ' '.join(random.sample(lines, 10)) # Increase prompt size\n", - " completion = generate_completion(prompt)\n", - " prompt_tokens = len(prompt.split())\n", - " completion_tokens = len(completion.split())\n", - " api_cost = (prompt_tokens + completion_tokens) * cost_per_token\n", - "\n", - " data.append([usage_date, user, version, prompt, completion, \n", - " prompt_tokens, completion_tokens, api_cost, latency])\n", - " \n", - " usage_date += increment\n", - "\n", - "df = pd.DataFrame(data, columns=['timestamp', 'username', 'model_version', 'prompt', 'completion', \n", - " 'prompt_tokens', 'completion_tokens', 'api_cost', 'latency'])\n", - "\n", - "predictions = weave.save(dataframe_to_arrow(df), 'predictions')\n", - "predictions" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "57fc86e0", - "metadata": {}, - "outputs": [], - "source": [ - "predictions = weave.legacy.ops.get(\"local-artifact:///predictions:latest/obj\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8049b6dd", - "metadata": {}, - "outputs": [], - "source": [ - "weave.legacy.panels.Board(\n", - " vars={\n", - " 'all_preds': predictions,\n", - " 'x_max': weave.legacy.ops.date_parse(str(end_date)),\n", - " 'x_min': lambda x_max: x_max - weave.legacy.ops.days(5),\n", - " 'predictions': lambda x_min: predictions.filter(lambda pred: pred['timestamp'] > x_min)\n", - " },\n", - " panels=[\n", - " weave.legacy.panels.BoardPanel(\n", - " lambda predictions, x_min, x_max: wandb.TimeSeries(\n", - " predictions,\n", - " x=lambda item: item[\"timestamp\"],\n", - " label=lambda item: item[\"model_version\"],\n", - " agg=lambda preds: preds.count(),\n", - " min_x=x_min,\n", - " max_x=x_max,\n", - " mark=\"bar\",\n", - " axis_labels={\"y\": \"num_preds\"},\n", - " ),\n", - " layout=weave.legacy.panels.BoardPanelLayout(x=0, y=0, w=12, h=6)\n", - " ),\n", - " weave.legacy.panels.BoardPanel(\n", - " lambda predictions, x_min, x_max: wandb.TimeSeries(\n", - " predictions,\n", - " x=lambda item: item[\"timestamp\"],\n", - " label=lambda item: item[\"username\"],\n", - " agg=lambda preds: preds.count(),\n", - " min_x=x_min,\n", - " max_x=x_max,\n", - " mark=\"bar\",\n", - " axis_labels={\"y\": \"num_preds\"},\n", - " ),\n", - " layout=weave.legacy.panels.BoardPanelLayout(x=12, y=0, w=12, h=6)\n", - " ),\n", - " weave.legacy.panels.BoardPanel(\n", - " lambda predictions, x_min, x_max: wandb.TimeSeries(\n", - " predictions,\n", - " x=lambda item: item[\"timestamp\"],\n", - " #label=lambda item: item[\"model_version\"],\n", - " agg=lambda preds: preds[\"api_cost\"].sum(),\n", - " min_x=x_min,\n", - " max_x=x_max,\n", - " mark=\"line\",\n", - " axis_labels={\"y\": \"api_cost\"},\n", - " ),\n", - " layout=weave.legacy.panels.BoardPanelLayout(x=0, y=6, w=8, h=6)\n", - " ),\n", - " weave.legacy.panels.BoardPanel(\n", - " lambda predictions, x_min, x_max: wandb.TimeSeries(\n", - " predictions,\n", - " x=lambda item: item[\"timestamp\"],\n", - " #label=lambda item: item[\"model_version\"],\n", - " agg=lambda preds: preds[\"prompt_tokens\"].sum() + preds['completion_tokens'].sum(),\n", - " min_x=x_min,\n", - " max_x=x_max,\n", - " mark=\"line\",\n", - " axis_labels={\"y\": \"total_tokens\"},\n", - " ),\n", - " layout=weave.legacy.panels.BoardPanelLayout(x=8, y=6, w=8, h=6)\n", - " ),\n", - " weave.legacy.panels.BoardPanel(\n", - " lambda predictions, x_min, x_max: wandb.TimeSeries(\n", - " predictions,\n", - " x=lambda item: item[\"timestamp\"],\n", - " #label=lambda item: item[\"model_version\"],\n", - " agg=lambda preds: preds[\"latency\"].avg(),\n", - " min_x=x_min,\n", - " max_x=x_max,\n", - " mark=\"line\",\n", - " axis_labels={\"y\": \"avg_latency\"},\n", - " ),\n", - " layout=weave.legacy.panels.BoardPanelLayout(x=16, y=6, w=8, h=6)\n", - " ),\n", - " weave.legacy.panels.BoardPanel(\n", - " lambda predictions, x_min, x_max: wandb.Distribution(\n", - " predictions,\n", - " value_fn=lambda pred: pred['api_cost'],\n", - " bin_size=0.2\n", - " ),\n", - " layout=weave.legacy.panels.BoardPanelLayout(x=0, y=12, w=8, h=6)\n", - " ),\n", - " weave.legacy.panels.BoardPanel(\n", - " lambda predictions, x_min, x_max: wandb.Distribution(\n", - " predictions,\n", - " value_fn=lambda pred: pred['prompt_tokens'] + pred['completion_tokens'],\n", - " bin_size=25\n", - " ),\n", - " layout=weave.legacy.panels.BoardPanelLayout(x=8, y=12, w=8, h=6)\n", - " ),\n", - " weave.legacy.panels.BoardPanel(\n", - " lambda predictions, x_min, x_max: wandb.Distribution(\n", - " predictions,\n", - " value_fn=lambda pred: pred['latency'],\n", - " bin_size=0.05\n", - " ),\n", - " layout=weave.legacy.panels.BoardPanelLayout(x=16, y=12, w=8, h=6)\n", - " ),\n", - " weave.legacy.panels.BoardPanel(\n", - " lambda predictions: predictions,\n", - " layout=weave.legacy.panels.BoardPanelLayout(x=0, y=18, w=24, h=12)\n", - " )\n", - " ]\n", - ")" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/experimental/skip_test/Monitor3.ipynb b/examples/experimental/skip_test/Monitor3.ipynb deleted file mode 100644 index 36c7bd69d62..00000000000 --- a/examples/experimental/skip_test/Monitor3.ipynb +++ /dev/null @@ -1,320 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "039f298a", - "metadata": {}, - "outputs": [], - "source": [ - "import pandas as pd\n", - "import weave\n", - "weave.use_frontend_devmode()\n", - "from weave.legacy.ops_arrow.list_ import dataframe_to_arrow\n", - "from weave.legacy.ecosystem import wandb" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5e0d6245", - "metadata": {}, - "outputs": [], - "source": [ - "import numpy as np\n", - "import pandas as pd\n", - "import random\n", - "from faker import Faker\n", - "from datetime import timedelta, time\n", - "\n", - "# Generate the version schedule\n", - "def generate_version_schedule(start_date, end_date):\n", - " current_date = start_date\n", - " versions = ['1.0']\n", - " version_schedule = {}\n", - " while current_date <= end_date:\n", - " date_versions = []\n", - " for version in versions:\n", - " service_percent = random.uniform(0, 1)\n", - " date_versions.append((version, service_percent))\n", - "\n", - " version_schedule[current_date.date()] = date_versions\n", - " current_date += timedelta(days=1)\n", - " if random.random() < 0.10: # 5% chance to introduce a new version each day\n", - " new_version = f'{float(versions[-1])+0.1:.1f}'\n", - " versions.append(new_version)\n", - " if len(versions) > 1 and random.random() < 0.10:\n", - " versions.pop(0)\n", - " return version_schedule\n", - "\n", - "# Generate the latency schedule\n", - "def generate_latency_schedule(start_date, end_date):\n", - " latency_schedule = {}\n", - " for current_date in pd.date_range(start_date, end_date):\n", - " base_latency = random.uniform(0.1, 1)\n", - " day_factor = random.uniform(0.5, 1.5)\n", - " month_factor = random.uniform(0.5, 1.5)\n", - " latency = base_latency * day_factor * month_factor\n", - " latency_schedule[current_date.date()] = latency\n", - " return latency_schedule\n", - "\n", - "# Generate the cost schedule\n", - "def generate_cost_schedule(start_date, end_date, cost_change_date):\n", - " cost_schedule = {}\n", - " current_date = start_date\n", - " cost_per_token = 0.01\n", - " while current_date <= end_date:\n", - " if current_date >= cost_change_date:\n", - " cost_per_token = 0.005\n", - " cost_schedule[current_date.date()] = cost_per_token\n", - " current_date += timedelta(days=1)\n", - " return cost_schedule\n", - "\n", - "def generate_user_usage_schedule(start_date, end_date, users):\n", - " user_usage_schedule = []\n", - " for user in users:\n", - " current_date = start_date + timedelta(days=random.randrange(90))\n", - " while current_date <= end_date:\n", - " usage_periods = random.randint(1, 30)\n", - " for _ in range(usage_periods):\n", - " period_length_timedelta = timedelta(hours=random.randint(1, 24 * 7))\n", - " rate = random.uniform(0.1, 10)\n", - " user_usage_schedule.append((current_date, user, period_length_timedelta, rate))\n", - " current_date += period_length_timedelta # Increment current_date\n", - " if current_date > end_date:\n", - " break\n", - " return user_usage_schedule" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9d6a0318", - "metadata": {}, - "outputs": [], - "source": [ - "# Define our fake users\n", - "fake = Faker()\n", - "users = [fake.user_name() for _ in range(100)]\n", - "\n", - "# Read the file and generate prompts\n", - "with open('/Users/shawn/Downloads/t8.shakespeare.txt', 'r') as f:\n", - " lines = f.read().split('\\n')\n", - "\n", - "# Define the time range\n", - "start_date = pd.to_datetime('2023-01-01', utc=True)\n", - "end_date = pd.to_datetime('2023-03-31', utc=True)\n", - "cost_change_date = pd.to_datetime('2023-02-15', utc=True)\n", - "\n", - "# Generate the schedules\n", - "version_schedule = generate_version_schedule(start_date, end_date)\n", - "latency_schedule = generate_latency_schedule(start_date, end_date)\n", - "cost_schedule = generate_cost_schedule(start_date, end_date, cost_change_date)\n", - "user_usage_schedule = generate_user_usage_schedule(start_date, end_date, users)\n", - "\n", - "# Helper function to generate a random completion\n", - "def generate_completion(prompt):\n", - " words = prompt.split()\n", - " completion = ' '.join(random.choices(words, k=int(len(words)* (random.random() + 0.1) * 10)))\n", - " return completion\n", - "\n", - "#latency_schedule" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a672c782", - "metadata": {}, - "outputs": [], - "source": [ - "import tqdm\n", - "data = []\n", - "for usage in tqdm.tqdm(user_usage_schedule):\n", - " usage_date, user, usage_period, rate = usage\n", - " \n", - " end_date = usage_date + usage_period\n", - " increment = timedelta(hours=rate)\n", - " \n", - " while usage_date < end_date:\n", - " # Find the version that was active during this usage\n", - " if usage_date.date() not in version_schedule:\n", - " break\n", - " active_versions = version_schedule[usage_date.date()]\n", - " #active_versions = [(version, percent) for date, version, percent in version_schedule if date.date() == usage_date.date()]\n", - " # Normalize the service percentages\n", - " total_percent = sum([percent for version, percent in active_versions])\n", - " if total_percent == 0:\n", - " continue\n", - " normalized_percentages = [percent / total_percent for version, percent in active_versions]\n", - "\n", - " version = np.random.choice([v for v, p in active_versions], p=normalized_percentages)\n", - " \n", - " # Find the cost during this usage\n", - " cost_per_token = cost_schedule[usage_date.date()]\n", - " \n", - " # Find the average latency during this usage\n", - " latency = latency_schedule[usage_date.date()]\n", - " latency *= (0.9 + random.random() *.2)\n", - " \n", - " prompt = ' '.join(random.sample(lines, 10)) # Increase prompt size\n", - " completion = generate_completion(prompt)\n", - " prompt_tokens = len(prompt.split())\n", - " completion_tokens = len(completion.split())\n", - " api_cost = (prompt_tokens + completion_tokens) * cost_per_token\n", - "\n", - " data.append([usage_date, user, version, prompt, completion, \n", - " prompt_tokens, completion_tokens, api_cost, latency])\n", - " \n", - " usage_date += increment\n", - "\n", - "df = pd.DataFrame(data, columns=['timestamp', 'username', 'model_version', 'prompt', 'completion', \n", - " 'prompt_tokens', 'completion_tokens', 'api_cost', 'latency'])\n", - "\n", - "predictions = weave.save(dataframe_to_arrow(df), 'predictions')\n", - "predictions" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "57fc86e0", - "metadata": {}, - "outputs": [], - "source": [ - "predictions = weave.legacy.ops.get(\"local-artifact:///predictions:latest/obj\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8049b6dd", - "metadata": {}, - "outputs": [], - "source": [ - "weave.legacy.panels.Board(\n", - " vars={\n", - " #'all_preds': predictions,\n", - " 'x_max': weave.legacy.ops.date_parse(str(end_date)),\n", - " 'x_min': lambda x_max: x_max - weave.legacy.ops.days(5),\n", - " 'predictions_query': weave.legacy.panels.Query(\n", - " predictions,\n", - " conditions=[\n", - " lambda query_input: weave.legacy.panels.QueryCondition(\n", - " expression=query_input['username'],\n", - " editor=weave.legacy.panels.SelectEditor(\n", - " choices=query_input['username']\n", - " )\n", - " )\n", - " ]),\n", - " 'predictions': lambda predictions_query: predictions_query.selected()\n", - " },\n", - " panels=[\n", - " weave.legacy.panels.BoardPanel(\n", - " lambda predictions, x_min, x_max: wandb.TimeSeries(\n", - " predictions,\n", - " x=lambda item: item[\"timestamp\"],\n", - " label=lambda item: item[\"model_version\"],\n", - " agg=lambda preds: preds.count(),\n", - " min_x=x_min,\n", - " max_x=x_max,\n", - " mark=\"bar\",\n", - " axis_labels={\"y\": \"num_preds\"},\n", - " ),\n", - " layout=weave.legacy.panels.BoardPanelLayout(x=0, y=0, w=12, h=6)\n", - " ),\n", - " weave.legacy.panels.BoardPanel(\n", - " lambda predictions, x_min, x_max: wandb.TimeSeries(\n", - " predictions,\n", - " x=lambda item: item[\"timestamp\"],\n", - " label=lambda item: item[\"username\"],\n", - " agg=lambda preds: preds.count(),\n", - " min_x=x_min,\n", - " max_x=x_max,\n", - " mark=\"bar\",\n", - " axis_labels={\"y\": \"num_preds\"},\n", - " ),\n", - " layout=weave.legacy.panels.BoardPanelLayout(x=12, y=0, w=12, h=6)\n", - " ),\n", - " weave.legacy.panels.BoardPanel(\n", - " lambda predictions, x_min, x_max: wandb.TimeSeries(\n", - " predictions,\n", - " x=lambda item: item[\"timestamp\"],\n", - " #label=lambda item: item[\"model_version\"],\n", - " agg=lambda preds: preds[\"api_cost\"].sum(),\n", - " min_x=x_min,\n", - " max_x=x_max,\n", - " mark=\"line\",\n", - " axis_labels={\"y\": \"api_cost\"},\n", - " ),\n", - " layout=weave.legacy.panels.BoardPanelLayout(x=0, y=6, w=8, h=6)\n", - " ),\n", - " weave.legacy.panels.BoardPanel(\n", - " lambda predictions, x_min, x_max: wandb.TimeSeries(\n", - " predictions,\n", - " x=lambda item: item[\"timestamp\"],\n", - " #label=lambda item: item[\"model_version\"],\n", - " agg=lambda preds: preds[\"prompt_tokens\"].sum() + preds['completion_tokens'].sum(),\n", - " min_x=x_min,\n", - " max_x=x_max,\n", - " mark=\"line\",\n", - " axis_labels={\"y\": \"total_tokens\"},\n", - " ),\n", - " layout=weave.legacy.panels.BoardPanelLayout(x=8, y=6, w=8, h=6)\n", - " ),\n", - " weave.legacy.panels.BoardPanel(\n", - " lambda predictions, x_min, x_max: wandb.TimeSeries(\n", - " predictions,\n", - " x=lambda item: item[\"timestamp\"],\n", - " #label=lambda item: item[\"model_version\"],\n", - " agg=lambda preds: preds[\"latency\"].avg(),\n", - " min_x=x_min,\n", - " max_x=x_max,\n", - " mark=\"line\",\n", - " axis_labels={\"y\": \"avg_latency\"},\n", - " ),\n", - " layout=weave.legacy.panels.BoardPanelLayout(x=16, y=6, w=8, h=6)\n", - " ),\n", - " weave.legacy.panels.BoardPanel(\n", - " lambda predictions, x_min, x_max: wandb.Distribution(\n", - " predictions,\n", - " value_fn=lambda pred: pred['api_cost'],\n", - " bin_size=0.2\n", - " ),\n", - " layout=weave.legacy.panels.BoardPanelLayout(x=0, y=12, w=8, h=6)\n", - " ),\n", - " weave.legacy.panels.BoardPanel(\n", - " lambda predictions, x_min, x_max: wandb.Distribution(\n", - " predictions,\n", - " value_fn=lambda pred: pred['prompt_tokens'] + pred['completion_tokens'],\n", - " bin_size=25\n", - " ),\n", - " layout=weave.legacy.panels.BoardPanelLayout(x=8, y=12, w=8, h=6)\n", - " ),\n", - " weave.legacy.panels.BoardPanel(\n", - " lambda predictions, x_min, x_max: wandb.Distribution(\n", - " predictions,\n", - " value_fn=lambda pred: pred['latency'],\n", - " bin_size=0.05\n", - " ),\n", - " layout=weave.legacy.panels.BoardPanelLayout(x=16, y=12, w=8, h=6)\n", - " ),\n", - " weave.legacy.panels.BoardPanel(\n", - " lambda predictions: predictions,\n", - " layout=weave.legacy.panels.BoardPanelLayout(x=0, y=18, w=24, h=12)\n", - " )\n", - " ]\n", - ")" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/experimental/skip_test/Multiple Y Axes.ipynb b/examples/experimental/skip_test/Multiple Y Axes.ipynb deleted file mode 100644 index ef49ac5d28e..00000000000 --- a/examples/experimental/skip_test/Multiple Y Axes.ipynb +++ /dev/null @@ -1,184 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "0903a3ef", - "metadata": {}, - "source": [ - "## Using this notebook to develop multiple y axes on Panel Plot" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f6c524b9", - "metadata": {}, - "outputs": [], - "source": [ - "import weave\n", - "import math\n", - "import random\n", - "weave.use_frontend_devmode()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f8d46616", - "metadata": {}, - "outputs": [], - "source": [ - "# PanelPlot supports multiple Y axes. Create a table with one row per series, with lists of x and y values.\n", - "data = []\n", - "for run in ['a1', 'a2', 'b1', 'b2', 'b3', 'c1']:\n", - " for x in range(100):\n", - " data.append({\n", - " 'group': run[0],\n", - " 'run': run,\n", - " 'step': x,\n", - " 'acc': random.random(),\n", - " })\n", - " \n", - "plot = weave.legacy.panels.Plot(data)\n", - "plot.set_x(lambda row: row['step'])\n", - "plot.set_y(lambda row: row['acc'])\n", - "plot.set_label(lambda row: row['run'])\n", - "weave.show(plot)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3d97c0fd", - "metadata": {}, - "outputs": [], - "source": [ - "# PanelPlot supports multiple Y axes. Create a table with one row per series, with lists of x and y values.\n", - "data = []\n", - "xs = [i / 1000.0 for i in range(0, 1000)]\n", - "for series in range(3):\n", - " data.append({\n", - " 'key': 'y%s' % series,\n", - " 'x': xs,\n", - " 'y': [math.sin(10 * x / (series+1)) for x in xs]\n", - " })\n", - "plot = weave.legacy.panels.Plot(data)\n", - "plot.set_x(lambda row: row['x'])\n", - "plot.set_y(lambda row: row['y'])\n", - "plot.set_label(lambda row: row['key'])\n", - "weave.show(plot)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "00fdefb5", - "metadata": {}, - "outputs": [], - "source": [ - "# Or you can unnest the data yourself (one row per point)\n", - "data = []\n", - "xs = [i / 1000.0 for i in range(0, 1000)]\n", - "for series in range(3):\n", - " for x in xs:\n", - " data.append({\n", - " 'key': 'y%s' % series,\n", - " 'x': x,\n", - " 'y': math.sin(10 * x / (series+1))\n", - " })\n", - "plot = weave.legacy.panels.Plot(data)\n", - "plot.set_x(lambda row: row['x'])\n", - "plot.set_y(lambda row: row['y'])\n", - "plot.set_label(lambda row: row['key'])\n", - "plot.set_mark('line')\n", - "weave.show(plot)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5f9b0741", - "metadata": {}, - "outputs": [], - "source": [ - "# If your series are column-oriented, you can use df.melt to orient the data to work with PanelPlot\n", - "\n", - "import pandas as pd\n", - "df = pd.DataFrame()\n", - "df['x'] = [i / 1000.0 for i in range(0, 1000)]\n", - "df['y1'] = df['x'].map(lambda x: math.sin(10 * x / 1))\n", - "df['y2'] = df['x'].map(lambda x: math.sin(10 * x / 2))\n", - "df['y3'] = df['x'].map(lambda x: math.sin(10 * x / 3))\n", - "\n", - "# df.melt converts to a table with three columns: x, series, y\n", - "df = df.melt(id_vars=['x'], value_vars=['y1', 'y2', 'y3'], var_name='series', value_name='y')\n", - "\n", - "multiple_y = df.to_dict('records')\n", - "plot = weave.legacy.panels.Plot(multiple_y)\n", - "plot.set_x(lambda row: row['x'])\n", - "plot.set_y(lambda row: row['y'])\n", - "plot.set_label(lambda row: row['series'])\n", - "plot.set_mark('line')\n", - "weave.show(plot)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "07752656", - "metadata": {}, - "outputs": [], - "source": [ - "# Or, leave the table in columnar form, and return arrays of the values you want for each series for PanelPlot\n", - "# x and label dimensions.\n", - "\n", - "# This relies on changes in this branch.\n", - "\n", - "# Note ExpressionEditor doesn't render list literals so it looks weird in the panel config. And we'll probably want\n", - "# to make the config editor more helpful, so that you choose labels along-side each y axis choice, or so that\n", - "# if you return a dict for Y we use the keys as the labels.\n", - "\n", - "import pandas as pd\n", - "df = pd.DataFrame()\n", - "df['x'] = [i / 100.0 for i in range(0, 100)]\n", - "df['x1'] = [i / 100.0 for i in range(0, 100)]\n", - "df['x2'] = [2 * i / 100.0 for i in range(0, 100)]\n", - "df['x3'] = [3 * i / 100.0 for i in range(0, 100)]\n", - "df['y1'] = df['x'].map(lambda x: math.sin(10 * x / 1))\n", - "df['y2'] = df['x'].map(lambda x: math.sin(10 * x / 2))\n", - "df['y3'] = df['x'].map(lambda x: math.sin(10 * x / 3))\n", - "multiple_y = df.to_dict('records')\n", - "\n", - "plot = weave.legacy.panels.Plot(multiple_y)\n", - "plot.set_x(lambda row: row['x'])\n", - "\n", - "# Return lists for y and label\n", - "plot.set_y(lambda row: [row['y1'], row['y2'], row['y3']])\n", - "plot.set_label(lambda row: ['y1', 'y2', 'y3'])\n", - "plot.set_mark('line')\n", - "weave.show(plot)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/experimental/skip_test/Mutation - Code Editor.ipynb b/examples/experimental/skip_test/Mutation - Code Editor.ipynb deleted file mode 100644 index cd57106914e..00000000000 --- a/examples/experimental/skip_test/Mutation - Code Editor.ipynb +++ /dev/null @@ -1,55 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "b856b674", - "metadata": {}, - "outputs": [], - "source": [ - "import weave\n", - "weave.use_frontend_devmode()\n", - "weave.capture_weave_server_logs()" - ] - }, - { - "cell_type": "markdown", - "id": "e056ac84", - "metadata": {}, - "source": [ - "Run the cell below, then click the gear icon to change to TextEditor. Your changes will be reflected in train.py" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c71000cc", - "metadata": {}, - "outputs": [], - "source": [ - "weave.legacy.ops.local_path('../weave/testdata/train.py')" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/experimental/skip_test/Mutation.ipynb b/examples/experimental/skip_test/Mutation.ipynb deleted file mode 100644 index a6c5bbf6796..00000000000 --- a/examples/experimental/skip_test/Mutation.ipynb +++ /dev/null @@ -1,54 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "b856b674", - "metadata": {}, - "outputs": [], - "source": [ - "import weave\n", - "weave.capture_weave_server_logs()" - ] - }, - { - "cell_type": "markdown", - "id": "e056ac84", - "metadata": {}, - "source": [ - "Change the \"name\" column in the panel below to StringEditor, then click a cell to edit the underlying csv." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c71000cc", - "metadata": {}, - "outputs": [], - "source": [ - "weave.legacy.ops.local_path('../weave/testdata/cereal.csv').readcsv" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/experimental/skip_test/OpenAI.ipynb b/examples/experimental/skip_test/OpenAI.ipynb deleted file mode 100644 index 76e86685c1e..00000000000 --- a/examples/experimental/skip_test/OpenAI.ipynb +++ /dev/null @@ -1,106 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "94f2344d", - "metadata": {}, - "outputs": [], - "source": [ - "import weave\n", - "from weave.legacy.ecosystem import openai" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "1abed8ce", - "metadata": {}, - "outputs": [], - "source": [ - "# Construct a dataset, we'll see if GPT-3 can do simple arithmetic\n", - "data = []\n", - "for i in range (101):\n", - " a = i\n", - " b = i % 9\n", - " r = a + b\n", - " data.append({'id': i, 'prompt': '%s + %s =' % (a, b), 'completion': ' %s' % r})\n", - "data = weave.save(data, name='openai-dataset')\n", - "data" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "348d63f0", - "metadata": {}, - "outputs": [], - "source": [ - "# Fine tune gpt-3 on our dataset.\n", - "\n", - "# Change this to \"openai.finetune_gpt3\" to run a real OpenAI fine-tune job\n", - "fine_tune = openai.finetune_gpt3_demo(data, {'n_epochs' : 2})\n", - "fine_tune" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "fe3d0872", - "metadata": {}, - "outputs": [], - "source": [ - "model = fine_tune.model()\n", - "model = weave.save(model, name='openai-model')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "7d656075", - "metadata": {}, - "outputs": [], - "source": [ - "# Show all the models we've trained, and \n", - "for version in weave.versions(model):\n", - " print(weave.expr(version))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "89bebbff", - "metadata": {}, - "outputs": [], - "source": [ - "# make some predictions using our new model\n", - "\n", - "panel = weave.legacy.panels.Table(['1 + 9 =', '2 + 14 ='])\n", - "panel.table_query.add_column(lambda row: row)\n", - "panel.table_query.add_column(lambda row: model.complete(row)['choices'][0]['text'])\n", - "weave.show(panel)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/experimental/skip_test/Oxford-IIIT Pet Dataset.ipynb b/examples/experimental/skip_test/Oxford-IIIT Pet Dataset.ipynb deleted file mode 100644 index 4a0836dccac..00000000000 --- a/examples/experimental/skip_test/Oxford-IIIT Pet Dataset.ipynb +++ /dev/null @@ -1,115 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "2cfb0934", - "metadata": {}, - "outputs": [], - "source": [ - "import weave\n", - "weave.use_frontend_devmode()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b81fda7c", - "metadata": {}, - "outputs": [], - "source": [ - "import os\n", - "im_names = os.listdir('/Users/shawn/datasets/oxford-iiit-pet-dataset/images')\n", - "breeds = set(i.rsplit('_', 1)[0] for i in im_names)\n", - "#sorted(breeds)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5e3925df", - "metadata": {}, - "outputs": [], - "source": [ - "import typing\n", - "type(typing.Optional[int])\n", - "type(typing._UnionGenericAlias)\n", - "# isinstance(typing.Optional, typing._SpecialForm)\n", - "# typing._SpecialForm.__class__" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9d20c10d", - "metadata": {}, - "outputs": [], - "source": [ - "#weave.legacy.ops.local_path('/Users/shawn/datasets')\n", - "# :(, broken right now" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "7f668d48", - "metadata": {}, - "outputs": [], - "source": [ - "from weave.legacy.ecosystem.shawn import petdataset\n", - "\n", - "data = petdataset.petdataset('/Users/shawn/datasets/oxford-iiit-pet-dataset')\n", - "#data" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "981fc71f", - "metadata": {}, - "outputs": [], - "source": [ - "from weave.legacy.ecosystem import wandb\n", - "wandb.MultiDistribution(data, value_fn=lambda item: item['breed'])" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b123d4f9", - "metadata": {}, - "outputs": [], - "source": [ - "# A PanelPlot only histogram\n", - "plot = weave.legacy.panels.Plot(\n", - " data,\n", - " y=lambda item: item['breed'],\n", - " groupby_y=True,\n", - " x=lambda group: group.count(),\n", - " mark='bar')\n", - "#plot" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/experimental/skip_test/Performance profiling.ipynb b/examples/experimental/skip_test/Performance profiling.ipynb deleted file mode 100644 index d128c91d462..00000000000 --- a/examples/experimental/skip_test/Performance profiling.ipynb +++ /dev/null @@ -1,153 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "c87095b6", - "metadata": {}, - "outputs": [], - "source": [ - "import weave\n", - "import numpy as np\n", - "import pyarrow as pa\n", - "import pyarrow.parquet as pq\n", - "\n", - "!rm -rf /tmp/local-artifacts\n", - "\n", - "t = pa.table({d: list(range(400)) * 1000 for d in \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\"})\n", - "weave_t = weave.legacy.ops.ArrowWeaveList(t)\n", - "weave.use(weave_t.groupby(lambda x: x['A']).map(lambda row: row.key()))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3a7726e4", - "metadata": {}, - "outputs": [], - "source": [ - "import os\n", - "import cProfile\n", - "import weave\n", - "from weave.legacy import storage\n", - "import random\n", - "import time\n", - "import pyarrow as pa\n", - "from PIL import Image\n", - "\n", - "def create_arrow_data(n_rows):\n", - " inner_count = int(n_rows / 25)\n", - " rotates = []\n", - " shears = []\n", - " x = []\n", - " y = []\n", - " random.seed(0)\n", - " for rotate in range(5):\n", - " for shear in range(5):\n", - " for i in range(inner_count):\n", - " rotates.append(rotate)\n", - " shears.append(shear)\n", - " x.append(random.choice(['a', 'b', 'c']))\n", - " y.append(random.randrange(10))\n", - " table = pa.table({\n", - " 'rotate': rotates,\n", - " 'shear': shears,\n", - " 'x': x,\n", - " 'y': y,\n", - " })\n", - " table_list = weave.legacy.ops.ArrowTableList(table)\n", - "\n", - " return storage.save(table_list)\n", - "\n", - "def create_data(n_rows):\n", - " inner_count = int(n_rows / 25)\n", - " ims = []\n", - " base_im = Image.linear_gradient(\"L\")\n", - " random.seed(0)\n", - " for rotate in range(5):\n", - " for shear in range(5):\n", - " for i in range(inner_count):\n", - " ims.append(\n", - " {\n", - " \"rotate\": rotate,\n", - " \"shear\": shear,\n", - " \"y\": random.choice([\"a\", \"b\", \"c\"]),\n", - " \"x\": random.randrange(10),\n", - "# \"image\": base_im.rotate(rotate * 4).transform(\n", - "# (256, 256),\n", - "# Image.AFFINE,\n", - "# (1, shear / 10, 0, 0, 1, 0),\n", - "# Image.BICUBIC,\n", - "# ),\n", - " }\n", - " )\n", - "\n", - " return storage.save(ims)\n", - "\n", - "\n", - "def run(ref):\n", - "# node = (\n", - "# weave.get(ref)\n", - "# .groupby(lambda row: weave.legacy.ops.dict_(rotate=row[\"rotate\"], shear=row[\"shear\"]))\n", - "# .map(lambda row: row.groupby(lambda row: row[\"y\"]))\n", - "# .dropna()\n", - "# .count()\n", - "# )\n", - " node = (\n", - " weave.get(ref)\n", - " # .map(lambda row: row['rotate'] + 1)\n", - " # .map(lambda row: row + 9)\n", - " .groupby(lambda row: weave.legacy.ops.dict_(rotate=row[\"rotate\"], shear=row[\"shear\"]))\n", - " [1]\n", - " .count()\n", - " )\n", - " result = weave.use(node)\n", - " print('Run result: %s' % result)\n", - "\n", - "\n", - "os.system(\"rm -rf /tmp/local-artifacts/*\")\n", - "ref = create_data(100000)\n", - "print(\"Data ref: \", ref)\n", - "start_time = time.time()\n", - "cProfile.run('run(\"%s\")' % ref, \"profile_stats\")\n", - "print('Run time: %ss' % (time.time() - start_time))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "44f3f7ba", - "metadata": {}, - "outputs": [], - "source": [ - "import pstats\n", - "from pstats import SortKey\n", - "p = pstats.Stats('profile_stats')\n", - "#p.strip_dirs().sort_stats(SortKey.CUMULATIVE).print_stats()\n", - "p.strip_dirs().sort_stats(SortKey.CUMULATIVE).print_callers('type_of')\n", - "#p.strip_dirs().sort_stats(SortKey.TIME).print_stats()" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/experimental/skip_test/ProdMonStreamTableIntro.ipynb b/examples/experimental/skip_test/ProdMonStreamTableIntro.ipynb deleted file mode 100644 index 878d9804b6e..00000000000 --- a/examples/experimental/skip_test/ProdMonStreamTableIntro.ipynb +++ /dev/null @@ -1,135 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "9617c234", - "metadata": {}, - "source": [ - "# StreamTable API" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "307ad3b7", - "metadata": {}, - "outputs": [], - "source": [ - "import weave\n", - "weave.use_frontend_devmode()\n", - "from weave.legacy.monitoring import StreamTable\n", - "import PIL\n", - "import numpy as np\n", - "\n", - "def make_image():\n", - " imarray = np.random.rand(100, 100, 3) * 255\n", - " return PIL.Image.fromarray(imarray.astype(\"uint8\")).convert(\"RGBA\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8c7b6c63", - "metadata": {}, - "outputs": [], - "source": [ - "table = StreamTable(\"pm_demo_all_2\", entity_name=\"timssweeney\", project_name=\"prodmon\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "643f46e3", - "metadata": {}, - "outputs": [], - "source": [ - "for _ in range(10):\n", - " table.log({\n", - " 'number': 1, \n", - " 'string': 'hello', \n", - " 'bool': True, \n", - " 'nested': {'data': 5}, \n", - " 'lists': list(range(10)), \n", - " 'custom': make_image()})\n", - "table" - ] - }, - { - "cell_type": "markdown", - "id": "56c50afb", - "metadata": {}, - "source": [ - "# Monitor Decorator" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "e7db9358", - "metadata": {}, - "outputs": [], - "source": [ - "from langchain.agents import load_tools\n", - "from langchain.agents import initialize_agent\n", - "from langchain.agents import AgentType\n", - "from langchain.llms import OpenAI\n", - "\n", - "llm = OpenAI(temperature=0)\n", - "tools = load_tools([\"llm-math\"], llm=llm)\n", - "agent = initialize_agent(tools, llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "90aba081", - "metadata": {}, - "outputs": [], - "source": [ - "from weave.legacy.monitoring import monitor\n", - "\n", - "@monitor(entity_name=\"timssweeney\", project_name=\"prodmon\")\n", - "def ask_llm_calculator(prompt, question):\n", - " return agent.run(prompt + \" \" + question)\n", - "\n", - "\n", - "ask_llm_calculator(\n", - " \"Please accurately answer the following question:\", \n", - " \"Find the square root of 5.4.\"\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f49b9b31", - "metadata": {}, - "outputs": [], - "source": [ - "# This API will change\n", - "ask_llm_calculator._stream_table._stream_table.rows()" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.8" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/experimental/skip_test/Shap.ipynb b/examples/experimental/skip_test/Shap.ipynb deleted file mode 100644 index f0885b69e2d..00000000000 --- a/examples/experimental/skip_test/Shap.ipynb +++ /dev/null @@ -1,60 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "eb58c451", - "metadata": {}, - "outputs": [], - "source": [ - "import weave\n", - "from weave.legacy.ecosystem import shap" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "bac8abef", - "metadata": {}, - "outputs": [], - "source": [ - "dataset = shap.ca_housing_dataset(1)\n", - "xy = shap.split_labels(dataset, 'MedHouseVal')\n", - "model = shap.xgboost_train(xy, {'learning_rate': 0.02})\n", - "\n", - "explain_res = model.shap_explain(xy['X'])" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "aa726585", - "metadata": {}, - "outputs": [], - "source": [ - "explain_res.summary_plot()" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/experimental/skip_test/StreamTables.ipynb b/examples/experimental/skip_test/StreamTables.ipynb deleted file mode 100644 index e83bc3909b9..00000000000 --- a/examples/experimental/skip_test/StreamTables.ipynb +++ /dev/null @@ -1,92 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "1333d291", - "metadata": {}, - "outputs": [], - "source": [ - "import weave\n", - "weave.use_frontend_devmode()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "17a80a08", - "metadata": {}, - "outputs": [], - "source": [ - "from weave.legacy.wandb_interface.wandb_stream_table import StreamTable" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "058fa5af", - "metadata": {}, - "outputs": [], - "source": [ - "import time\n", - "entity_name = \"timssweeney\"\n", - "project_name = \"stream_tables\"\n", - "run_name = \"tt\" + str(int(time.time()))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f0691851", - "metadata": {}, - "outputs": [], - "source": [ - "st = StreamTable(run_name, project_name, entity_name)\n", - "\n", - "for i in range(10):\n", - " st.log({'a': i})" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "e0a597a5", - "metadata": {}, - "outputs": [], - "source": [ - "st" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "fb42732f", - "metadata": {}, - "outputs": [], - "source": [ - "weave.legacy.ops.project(entity_name, project_name).run(run_name).history2()" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.8" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/experimental/skip_test/TimeSeries.ipynb b/examples/experimental/skip_test/TimeSeries.ipynb deleted file mode 100644 index 251def88910..00000000000 --- a/examples/experimental/skip_test/TimeSeries.ipynb +++ /dev/null @@ -1,148 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "e4885744", - "metadata": {}, - "outputs": [], - "source": [ - "import weave\n", - "weave.use_frontend_devmode()\n", - "from weave.legacy import ops_arrow\n", - "from weave.legacy.ecosystem import wandb" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "50e46344", - "metadata": {}, - "outputs": [], - "source": [ - "import datetime\n", - "import random\n", - "random.seed(1)\n", - "\n", - "min_x = datetime.datetime(2023, 3, 22, 11, 0, 0)\n", - "max_x = min_x + datetime.timedelta(hours=1)\n", - "\n", - "users = ['nick', 'shawn', 'stacey', 'tim', 'danny']\n", - "model_versions = ['v1', 'v2']\n", - "\n", - "num_requests = 10000\n", - "\n", - "def random_datetime_in_range() -> datetime.datetime:\n", - " min_timestamp = min_x.timestamp()\n", - " max_timestamp = max_x.timestamp()\n", - " random_timestamp = random.uniform(min_timestamp, max_timestamp)\n", - " return datetime.datetime.fromtimestamp(random_timestamp)\n", - "\n", - "def random_suggestion(n: int) -> str:\n", - " result = ''\n", - " for i in range(n):\n", - " result += random.choice('abcdefghijklmnopqrstuvwxyz')\n", - " return result\n", - "\n", - "\n", - "items = weave.save(\n", - " ops_arrow.to_arrow([\n", - " {\n", - " \"pred\": random_suggestion(7),\n", - " \"time\": random_datetime_in_range(),\n", - " \"user\": random.choice(users),\n", - " \"version\": random.choice(model_versions),\n", - " \"accepted\": random.choice([1, 1, 1, 0, 0]),\n", - " } \n", - " for _ in range(num_requests)\n", - " ])\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "fb863d87", - "metadata": {}, - "outputs": [], - "source": [ - "panel = weave.legacy.panels.Group(\n", - " items={\n", - " \"num_preds\": weave.legacy.panels.LabeledItem(\n", - " label=\"Predictions by version over time\",\n", - " item=wandb.TimeSeries(\n", - " items,\n", - " x=lambda item: item[\"time\"],\n", - " label=lambda item: item[\"version\"],\n", - " agg=lambda preds: preds.count(),\n", - " min_x=min_x,\n", - " max_x=max_x,\n", - " mark=\"bar\",\n", - " axis_labels={\"y\": \"num_preds\"},\n", - " ),\n", - " ),\n", - " \"bottom_row\": weave.legacy.panels.Group(\n", - " style=\"height: 400px;\",\n", - " preferHorizontal=True,\n", - " items={\n", - " \"preds_by_user\": weave.legacy.panels.LabeledItem(\n", - " label=\"Predictions by user over time\",\n", - " item=wandb.TimeSeries(\n", - " items,\n", - " x=lambda item: item[\"time\"],\n", - " label=lambda item: item[\"user\"],\n", - " agg=lambda preds: preds.count(),\n", - " min_x=min_x,\n", - " max_x=max_x,\n", - " mark=\"bar\",\n", - " axis_labels={\"y\": \"num_preds\"},\n", - " ),\n", - " ),\n", - " \"acceptance_rate\": weave.legacy.panels.LabeledItem(\n", - " label=\"Acceptance rate over time by version\",\n", - " item=wandb.TimeSeries(\n", - " items,\n", - " x=lambda item: item[\"time\"],\n", - " label=lambda item: item[\"version\"],\n", - " agg=lambda preds: preds[\"accepted\"].sum() / preds.count(),\n", - " min_x=min_x,\n", - " max_x=max_x,\n", - " mark=\"line\",\n", - " axis_labels={\"y\": \"acceptance_rate\"},\n", - " ),\n", - " ),\n", - " },\n", - " ),\n", - " }\n", - ")\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "35c7e310", - "metadata": {}, - "outputs": [], - "source": [ - "panel" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "50b1be3e", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/experimental/skip_test/Untitled.ipynb b/examples/experimental/skip_test/Untitled.ipynb deleted file mode 100644 index a3755ff5feb..00000000000 --- a/examples/experimental/skip_test/Untitled.ipynb +++ /dev/null @@ -1,51 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "5ca8d4be", - "metadata": {}, - "outputs": [], - "source": [ - "import csv\n", - "import weave" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "492a6655", - "metadata": {}, - "outputs": [], - "source": [ - "#help(csv.reader)\n", - "# reader = csv.reader(open('/Users/shawn/Downloads/wandb_export_2023-03-30T15_21_00.493-04_00.csv'))\n", - "# for row in reader:\n", - "# print(row)\n", - "#help(csv.reader)\n", - "weave.legacy.ops.local_path('/Users/shawn/code2/weave/weave/testdata/cereal.csv').readcsv()" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/experimental/skip_test/Vectorizing.ipynb b/examples/experimental/skip_test/Vectorizing.ipynb deleted file mode 100644 index 456c64262a8..00000000000 --- a/examples/experimental/skip_test/Vectorizing.ipynb +++ /dev/null @@ -1,91 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "5f10d941", - "metadata": {}, - "outputs": [], - "source": [ - "import weave\n", - "import time\n", - "from weave.legacy import storage\n", - "from weave.legacy.ops_primitives import geom" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9f3026bd", - "metadata": {}, - "outputs": [], - "source": [ - "segs = []\n", - "for i in range(1000000):\n", - " segs.append(geom.LineSegment(i*0.1, i+0.2, i+0.3, i*0.5))\n", - "l = weave.save(segs)\n", - "arr = weave.legacy.ops.ArrowArrayList(storage.to_arrow(segs))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "7544d866", - "metadata": {}, - "outputs": [], - "source": [ - "# pure python\n", - "start_time = time.time()\n", - "sum(seg.length.resolve_fn(seg) for seg in segs)\n", - "print('TIME: ', time.time() - start_time)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "11c2a9b6", - "metadata": {}, - "outputs": [], - "source": [ - "# weave list\n", - "start_time = time.time()\n", - "weave.use(l.map(lambda seg: seg.length()).sum())\n", - "print('TIME: ', time.time() - start_time)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "71816731", - "metadata": {}, - "outputs": [], - "source": [ - "# weave vectorized arrow\n", - "start_time = time.time()\n", - "weave.use(arr.map(lambda seg: seg.length()).sum())\n", - "print('TIME: ', time.time() - start_time)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/experimental/skip_test/WB data.ipynb b/examples/experimental/skip_test/WB data.ipynb deleted file mode 100644 index ce5c1633310..00000000000 --- a/examples/experimental/skip_test/WB data.ipynb +++ /dev/null @@ -1,80 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "bb76d9d8", - "metadata": {}, - "outputs": [], - "source": [ - "import weave\n", - "weave.use_frontend_devmode()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "54243e21", - "metadata": {}, - "outputs": [], - "source": [ - "project = weave.legacy.ops.project('shawn', 'fasion-sweep')\n", - "runs = project.runs()\n", - "\n", - "panel = weave.legacy.panels.Group(\n", - " preferHorizontal=True,\n", - " items={\n", - " \"sidebar\": weave.legacy.panels.Group(\n", - " style=\"width: 200px; padding: 16px;\",\n", - " items={\n", - " \"run\": weave.legacy.panels.ObjectPicker(runs, label='run'),\n", - " }\n", - " ),\n", - " #\"main\": lambda sidebar: project.run(sidebar.config.items['run_id'].config.choice).history()\n", - " \"main\": lambda sidebar: sidebar.config.items['run'].config.choice.history()\n", - " }\n", - ")\n", - "panel" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5a771433", - "metadata": {}, - "outputs": [], - "source": [ - "project = weave.legacy.ops.project('shawn', 'fasion-sweep')\n", - "run_ids = project.runs().id()\n", - "\n", - "panel = weave.legacy.panels.Group(\n", - " preferHorizontal=True,\n", - " items={\n", - " \"sidebar\": weave.legacy.panels.Group(\n", - " style=\"width: 200px; padding: 16px;\",\n", - " items={\n", - " \"run_id\": weave.legacy.panels.ObjectPicker(run_ids, label='run_id'),\n", - " }\n", - " ),\n", - " #\"main\": lambda sidebar: project.run(sidebar.config.items['run_id'].config.choice).history()\n", - " \"main\": lambda sidebar: weave.legacy.panels.Plot(\n", - " project.run(sidebar.config.items['run_id'].config.choice).history(),\n", - " x=lambda row: row['_step'],\n", - " y=lambda row: row['loss'],\n", - " )\n", - " }\n", - ")\n", - "#panel" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/experimental/skip_test/Weave geo data.ipynb b/examples/experimental/skip_test/Weave geo data.ipynb deleted file mode 100644 index 7b63ffb245e..00000000000 --- a/examples/experimental/skip_test/Weave geo data.ipynb +++ /dev/null @@ -1,106 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "8d5be6da", - "metadata": {}, - "outputs": [], - "source": [ - "import weave\n", - "import pandas as pd\n", - "from weave.legacy.ecosystem import wandb\n", - "weave.use_frontend_devmode()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "90518a5e", - "metadata": {}, - "outputs": [], - "source": [ - "data = pd.read_csv(\"/Users/shawn/code2/weave/database.csv\").to_dict(orient='records')\n", - "data = data[:1000]\n", - "weave.show(data)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f643bb3f", - "metadata": {}, - "outputs": [], - "source": [ - "#wandb.Scatter(data, x_fn=lambda item: item[\"Longitude\"], y_fn=lambda item: item[\"Latitude\"])" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b94b992b", - "metadata": {}, - "outputs": [], - "source": [ - "# wandb.plotly_geo([\n", - "# {'lat': 35, 'long': 39, 'color': 0.1},\n", - "# {'lat': 19, 'long': 24, 'color': 100}])" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "6b31859b", - "metadata": {}, - "outputs": [], - "source": [ - "#wandb.Geo(data)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "872cb8d0", - "metadata": {}, - "outputs": [], - "source": [ - "weave.legacy.panels.Group(\n", - " items={\n", - " 'geo': wandb.Geo(data,\n", - " x_fn=lambda item: item['Longitude'],\n", - " y_fn=lambda item: item[\"Latitude\"],\n", - " color_fn=lambda item: item[\"Magnitude\"]),\n", - " 'table': lambda geo: weave.legacy.panels.LabeledItem(\n", - " label='Selected items',\n", - " item=weave.legacy.panels.Group(\n", - " style=\"height: 400px;\",\n", - " preferHorizontal=True,\n", - " items={\n", - " 'table': geo.selected()\n", - " }))\n", - " })\n" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/experimental/skip_test/art_explore.ipynb b/examples/experimental/skip_test/art_explore.ipynb deleted file mode 100644 index 2b3ae1221c6..00000000000 --- a/examples/experimental/skip_test/art_explore.ipynb +++ /dev/null @@ -1,141 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "3df06c92", - "metadata": {}, - "source": [ - "# Art Exploration Dashboard\n", - "\n", - "This quick example programmatically buids a Weave Dashboard to explore and remix visual art.\n", - "* generate creative descriptions of artwork given some famous artists as reference points/inspiration\n", - "* create a Table (one column of text) of these art prompts (named \"style_riff\")\n", - "* use Stable Diffusion to generate art based on these prompts (\"riff_art\" column)\n", - "* inverse-generate a prompt which would lead to that image (and perhaps describe it better? \"art_desc\" column)\n", - "* imagine a perfect title for an artwork with that description (\"desc title\")\n", - "* finally, generate a remixed artwork for that title (\"remixed_art\"), except with more of the generated color specified in the horizontal panel (itself generated from the RGB values in the left sidebar)\n", - "\n", - "## Play with these parameters\n", - "\n", - "* the artist names and artist_id for which artist is the starting point\n", - "* the \"palette\" colors—these are the rows in the Dashboard\n", - "* the number of works (must be <= the number of colors in the palette and the number of artists\n", - "* the RGB values (0, 255)—these are combined to generate the \"color\" which is amplified in the remix art. Sometimes the color names are returned with extra words :)\n", - "* any of the prompts used to generate images or titles!\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "1ef13bdf", - "metadata": {}, - "outputs": [], - "source": [ - "import weave\n", - "from weave import weave_internal\n", - "from weave.legacy.ecosystem.all import langchain" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c765a214", - "metadata": {}, - "outputs": [], - "source": [ - "weave.legacy.panels.Board(\n", - " vars={\n", - " \"chat_model\": langchain.chat_openai(\"gpt-3.5-turbo\", 0.7),\n", - " \"artists\" : [\"René Magritte\", \"Josephine Wall\", \"Android Jones\", \"Alphonse Mucha\", \"Claude Monet\"],\n", - " \"artist_id\" : 0,\n", - " \"num_works\": 3,\n", - " \"palette\" : [\"red\", \"orange\", \"yellow\", \"green\", \"blue\", \"violet\"],\n", - " \"style_riff\": \n", - " lambda chat_model, num_works, artists, palette, artist_id:\n", - " weave.legacy.ops.range(0, num_works, 1).map(\n", - " lambda row:\n", - " chat_model.predict(weave_internal.const(\"You are an expert art historian. In a sophisticated, avant-garde art museum, you see pieces in \")\n", - " + palette[row] + \" by \" + artists[artist_id]\n", - " + \", which has a fantastic contrast and synergy with the work of\")\n", - " \n", - " ),\n", - " \"riff_art\":\n", - " lambda style_riff, num_works:\n", - " weave.legacy.ops.range(0, num_works, 1).map(\n", - " lambda row: style_riff[row]).stable_diffusion(),\n", - " \"art_desc\":\n", - " lambda riff_art, num_works:\n", - " weave.legacy.ops.range(0, num_works, 1).map(\n", - " lambda row: riff_art[row]).img2prompt(),\n", - " \"desc_title\":\n", - " lambda art_desc, num_works, chat_model:\n", - " weave.legacy.ops.range(0, num_works, 1).map(\n", - " lambda row: chat_model.predict(weave_internal.const(\"Art description: \")\n", - " + art_desc[row] + \". Perfect title:\")),\n", - " \"R\" : 168,\n", - " \"G\" : 156,\n", - " \"B\" : 234,\n", - " \"color\" : lambda chat_model, R, G, B:\n", - " chat_model.predict(weave_internal.const(\"RGB value: (\")\n", - " + R.toString() + \",\" + G.toString() + \",\" + B.toString()\n", - " + \"). Precise color name:\"),\n", - " \"remixed_art\":\n", - " lambda desc_title, num_works, color:\n", - " weave.legacy.ops.range(0, num_works, 1).map(\n", - " lambda row: weave_internal.const(\"a beautiful painting titled \")\n", - " + desc_title[row] + \" with more \" + color).stable_diffusion()\n", - " \n", - " },\n", - " panels=[\n", - " weave.legacy.panels.BoardPanel(\n", - " lambda style_riff: weave.legacy.panels.Table(style_riff, columns=[lambda col: col]),\n", - " layout=weave.legacy.panels.BoardPanelLayout(x=0, y=0, w=4, h=16)\n", - " ),\n", - " weave.legacy.panels.BoardPanel(\n", - " lambda riff_art: weave.legacy.panels.Table(riff_art, columns=[lambda col: col]),\n", - " layout=weave.legacy.panels.BoardPanelLayout(x=4, y=0, w=6, h=16)\n", - " ),\n", - " weave.legacy.panels.BoardPanel(\n", - " lambda art_desc: weave.legacy.panels.Table(art_desc, columns=[lambda col: col]),\n", - " layout=weave.legacy.panels.BoardPanelLayout(x=10, y=0, w=4, h=16)\n", - " ),\n", - " weave.legacy.panels.BoardPanel(\n", - " lambda desc_title: weave.legacy.panels.Table(desc_title, columns=[lambda col: col]),\n", - " layout=weave.legacy.panels.BoardPanelLayout(x=14, y=0, w=4, h=16)\n", - " ),\n", - " weave.legacy.panels.BoardPanel(\n", - " lambda remixed_art: weave.legacy.panels.Table(remixed_art, columns=[lambda col: col]),\n", - " layout=weave.legacy.panels.BoardPanelLayout(x=18, y=0, w=6, h=16)\n", - " ),\n", - " weave.legacy.panels.BoardPanel(\n", - " lambda color: weave.legacy.panels.StringEditor(color),\n", - " layout=weave.legacy.panels.BoardPanelLayout(x=7, y=16, w=10, h=4)\n", - " ),\n", - " \n", - " ]\n", - ")" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/experimental/skip_test/branching_runs.ipynb b/examples/experimental/skip_test/branching_runs.ipynb deleted file mode 100644 index 86b3c167e31..00000000000 --- a/examples/experimental/skip_test/branching_runs.ipynb +++ /dev/null @@ -1,215 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "930f7487", - "metadata": {}, - "outputs": [], - "source": [ - "import time\n", - "import weave\n", - "from weave.legacy.ops_primitives import ArrowWeaveList\n", - "from weave.legacy.ops_domain import RunSegment\n", - "from weave.legacy import storage\n", - "from weave import publish, type_of\n", - "from weave.legacy.weave_types import List\n", - "import typing\n", - "import time\n", - "import sys\n", - "import numpy as np\n", - "from weave.legacy.ops import to_arrow\n", - "\n", - "import logging\n", - "import pyarrow as pa\n", - "\n", - "\n", - "logger = logging.getLogger(\"run_segment\")\n", - "handler = logging.StreamHandler(stream=sys.stdout)\n", - "handler.setFormatter(logging.Formatter(\"%(asctime)s - %(message)s\"))\n", - "logger.addHandler(handler)\n", - "\n", - "# set to logging.INFO for more verbose profiling\n", - "logger.setLevel(logging.ERROR)\n", - "\n", - "# serializer = publish # uses w&b artifacts intead of local artifacts\n", - "serializer = storage.save\n", - "\n", - "N_NUMERIC_METRICS = 99 # number of numerical columns in the metrics table\n", - "\n", - "\n", - "def random_metrics(n: int = 10, starting_step: int = 0, delta_step: int = 1):\n", - " \"\"\"Create an array of metrics of length n starting from step starting_index.\"\"\"\n", - " if n <= 0:\n", - " raise ValueError(\"n must be at least 1\")\n", - " if starting_step < 0:\n", - " raise ValueError(\"starting index must be at least 0\")\n", - " if delta_step < 1:\n", - " raise ValueError(\"delta_step must be an integer greater than or equal to 1.\")\n", - " data = {\n", - " \"step\": np.arange(starting_step, starting_step + n * delta_step, delta_step),\n", - " \"string_col\": np.random.choice(list(\"ABCDEFGHIJKLMNOPQRSTUVWXYZ\"), n),\n", - " }\n", - " for j in range(N_NUMERIC_METRICS):\n", - " data[f\"metric{j}\"] = np.random.random(n) + np.sin(data['step'] * np.pi / 1e5)\n", - " data[f\"metric{j}\"][n // 10] = 10.\n", - " \n", - " return ArrowWeaveList(pa.table(data))\n", - "\n", - "\n", - "def create_branch(\n", - " name: str,\n", - " previous_segment: typing.Optional[RunSegment] = None,\n", - " length=10,\n", - " previous_segment_branch_frac=0.8,\n", - ") -> RunSegment:\n", - " \"\"\"Create a new segment and optionally attach it to a previous segment.\n", - "\n", - " Parameters\n", - " ----------\n", - " name: str\n", - " The name of the segment.\n", - " previous_segment: Optional[RunSegment], default None.\n", - " The parent run segment. If this is a root run segment, use None.\n", - " length: int, default = 10\n", - " The number of history rows to generate for the segment.\n", - " previous_segment_branch_frac: float satisfying 0 < branch_frac <= 1.\n", - " Parameter describing where in the previous segment to set the branch point.\n", - " A previous_segment_branch_frac of 0 sets the branch point at the previous\n", - " segment's root, whereas a previous_segment_branch_frac of 1 sets the branch\n", - " point at the end of the previous segment. A previous_segment_branch_frac of\n", - " 0.5 would include half of the previous segment's metric rows.\n", - "\n", - " Returns\n", - " -------\n", - " segment: RunSegment\n", - " The new segment.\n", - " \"\"\"\n", - " if not (0 < previous_segment_branch_frac <= 1):\n", - " raise ValueError(\"branch_frac must satisfy 0 < branch_frac <= 1\")\n", - "\n", - " if length <= 0:\n", - " raise ValueError(\"Length must be greater than 0.\")\n", - "\n", - " if previous_segment:\n", - " previous_metrics = previous_segment.metrics\n", - " n_previous_metrics = len(previous_metrics)\n", - " if n_previous_metrics > 0:\n", - " previous_segment_branch_index = (\n", - " int(previous_segment_branch_frac * n_previous_metrics) - 1\n", - " )\n", - "\n", - " # this run segment has a different root than the previous one\n", - " if previous_segment_branch_index < 0:\n", - " raise ValueError(\n", - " f\"Invalid branch point on RunSegment: previous_segment_branch_index \"\n", - " f\"{previous_segment_branch_index} must be between 0 and {len(previous_metrics) - 1}\"\n", - " )\n", - "\n", - " previous_segment_branch_step = (\n", - " previous_metrics._index(0)[\"step\"] + previous_segment_branch_index\n", - " )\n", - "\n", - " ref = storage.save(previous_segment)\n", - " new_metrics = random_metrics(\n", - " n=length, starting_step=previous_segment_branch_step + 1\n", - " )\n", - "\n", - " return RunSegment(name, ref.uri, previous_segment_branch_index, new_metrics)\n", - " return RunSegment(name, None, 0, random_metrics(length, 0))\n", - "\n", - "\n", - "def create_experiment(\n", - " num_steps: int, num_runs: int, branch_frac: float = 0.8\n", - ") -> typing.Optional[RunSegment]:\n", - " num_steps_per_run = num_steps // num_runs\n", - " segment = None\n", - " for i in range(num_runs):\n", - " segment = create_branch(\n", - " f\"branch {i}\",\n", - " segment,\n", - " length=num_steps_per_run,\n", - " previous_segment_branch_frac=branch_frac,\n", - " )\n", - " return segment\n", - "\n", - "last_segment = create_experiment(1000000, 100, 0.8)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "139bc4d0", - "metadata": {}, - "outputs": [], - "source": [ - "weave.show(last_segment)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "2ca43fb6", - "metadata": {}, - "outputs": [], - "source": [ - "weave.show(last_segment.experiment())" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5d8b6719", - "metadata": {}, - "outputs": [], - "source": [ - "plot = weave.legacy.panels.Plot(last_segment.experiment())\n", - "series = plot.series[-1]\n", - "series2 = series.clone()\n", - "\n", - "plot.add_series(series2)\n", - "plot.set_x(lambda row: weave.legacy.ops.number_bin(row['step'], weave.legacy.ops.numbers_bins_equal([1, 2000], 2)))\n", - "\n", - "series.set_y(lambda row: weave.legacy.ops.numbers_avg(row['metric0']))\n", - "series.set_mark_constant('line')\n", - "\n", - "series2.set_y(lambda row: weave.legacy.ops.numbers_min(row['metric0']))\n", - "series2.set_y2(lambda row: weave.legacy.ops.numbers_max(row['metric0']))\n", - "series2.set_mark_constant('area')\n", - "\n", - "plot.groupby_x()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ee4656e4", - "metadata": {}, - "outputs": [], - "source": [ - "plot" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/experimental/skip_test/image_gen_replicate.ipynb b/examples/experimental/skip_test/image_gen_replicate.ipynb deleted file mode 100644 index dad519c4e85..00000000000 --- a/examples/experimental/skip_test/image_gen_replicate.ipynb +++ /dev/null @@ -1,77 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "a1d79cb9", - "metadata": {}, - "source": [ - "# Image Generation with Replicate\n", - "\n", - "Convert a text prompt to an image and back to text with img2prompt or clip captioning." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "7c49c962", - "metadata": {}, - "outputs": [], - "source": [ - "# weave-test-skip-all: replicate API is slow and fails sometimes\n", - "import os\n", - "from weave.legacy.ecosystem import replicate as weave_replicate" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "38f878f2", - "metadata": {}, - "outputs": [], - "source": [ - "weave_replicate.stable_diffusion(\"Your life is a sheep herd. You're the driver.\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "6e848393", - "metadata": {}, - "outputs": [], - "source": [ - "weave_replicate.img2prompt(weave_replicate.stable_diffusion(\"Your life is a sheep herd. You're the driver.\"))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "50596314", - "metadata": {}, - "outputs": [], - "source": [ - "weave_replicate.clip_prefix_caption(weave_replicate.stable_diffusion(\"Your life is a sheep herd. You're the driver.\"))" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/experimental/skip_test/langchain docbot.ipynb b/examples/experimental/skip_test/langchain docbot.ipynb deleted file mode 100644 index f9fe0650898..00000000000 --- a/examples/experimental/skip_test/langchain docbot.ipynb +++ /dev/null @@ -1,89 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "0d6220cd", - "metadata": {}, - "outputs": [], - "source": [ - "import weave\n", - "from weave import trace\n", - "weave.use_frontend_devmode()\n", - "from weave.legacy.ecosystem.all import langchain" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a674fd63", - "metadata": {}, - "outputs": [], - "source": [ - "from langchain.chat_models import ChatOpenAI\n", - "#ChatOpenAI(model_name='gpt-3.5-turbo').query('hey whats up')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f8cff0cd", - "metadata": {}, - "outputs": [], - "source": [ - "weave.save([\n", - " 'how do I use weave?',\n", - " 'what products does W&B have?',\n", - " 'how do I get a sweep ID from a run?'\n", - "], 'questions')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a47df334", - "metadata": {}, - "outputs": [], - "source": [ - "from langchain.docstore.document import Document\n", - "documents = weave.legacy.ops.local_path('/Users/shawn/Downloads/documents.jsonl').readjsonl().limit(1000)\n", - "docs = weave.save([Document(**d) for d in weave.use(documents)], 'documents:latest')\n", - "#docs" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "865af8ce", - "metadata": {}, - "outputs": [], - "source": [ - "db = langchain.faiss_from_documents(docs, langchain.openai_embeddings())\n", - "llm = langchain.chat_openai('gpt-3.5-turbo', 0.7)\n", - "qa = langchain.retrieval_qa_from_chain_type(llm, 'stuff', db)\n", - "qa.run('lets try again 2')" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/experimental/skip_test/plot_selection.ipynb b/examples/experimental/skip_test/plot_selection.ipynb deleted file mode 100644 index 624fa155ee2..00000000000 --- a/examples/experimental/skip_test/plot_selection.ipynb +++ /dev/null @@ -1,63 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "c09f7649", - "metadata": {}, - "outputs": [], - "source": [ - "import weave\n", - "# weave.use_frontend_devmode()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a6b83817", - "metadata": {}, - "outputs": [], - "source": [ - "panel = weave.legacy.panels.Group(\n", - " items={\n", - " \"plot\": weave.legacy.panels.Plot(weave.legacy.ops.range(1, 100, 1),\n", - " x=lambda row: row,\n", - " y=lambda row: row ** 2,\n", - " ),\n", - " \"table\": lambda plot: weave.legacy.panels.panel_plot.selected_rows(plot)\n", - " })\n", - "\n", - "panel" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "55f43427", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/experimental/skip_test/slurm.ipynb b/examples/experimental/skip_test/slurm.ipynb deleted file mode 100644 index cf1ebf69670..00000000000 --- a/examples/experimental/skip_test/slurm.ipynb +++ /dev/null @@ -1,66 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "8817764c", - "metadata": {}, - "outputs": [], - "source": [ - "import weave\n", - "from weave.legacy.ecosystem import slurm" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "cda9e9a3", - "metadata": {}, - "outputs": [], - "source": [ - "#slurm.slurm('http://localhost:8899').jobs()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9bee4444", - "metadata": {}, - "outputs": [], - "source": [ - "#slurm.slurm('http://localhost:8899').nodes()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "e69a2f48", - "metadata": {}, - "outputs": [], - "source": [ - "slurm.slurm('http://localhost:8899')" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/experimental/skip_test/weave_engine_tracing.ipynb b/examples/experimental/skip_test/weave_engine_tracing.ipynb deleted file mode 100644 index d409656ab68..00000000000 --- a/examples/experimental/skip_test/weave_engine_tracing.ipynb +++ /dev/null @@ -1,126 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "de9a4837", - "metadata": {}, - "source": [ - "## Visualize Weave engine traces using Weave\n", - "\n", - "To produce data, run the Weave server with Datadog logging on, and WEAVE_TRACE_STREAM=//" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c62364f5", - "metadata": {}, - "outputs": [], - "source": [ - "import weave\n", - "from weave.legacy.wandb_interface.wandb_stream_table import StreamTable" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8d099de8", - "metadata": {}, - "outputs": [], - "source": [ - "# set this to the StreamTable your server logs to. Or run as is to use public test data.\n", - "spans = StreamTable('shawn/weave-trace-test2/test15').rows()\n", - "\n", - "board = weave.legacy.panels.Board(\n", - " vars={\n", - " 'all_spans': spans,\n", - " 'trace_id': '',\n", - " 'selected_spans': lambda trace_id, all_spans: all_spans.filter(lambda row: row['trace_id'] == trace_id)\n", - " },\n", - " panels=[\n", - " weave.legacy.panels.BoardPanel(\n", - " # TODO: have to compute all columns we want here, there's no way to\n", - " # tell js or python to do auto-columns behavior.\n", - " lambda all_spans: all_spans,\n", - " id='table',\n", - " layout=weave.legacy.panels.BoardPanelLayout(x=0, y=0, w=24, h=6)\n", - " ),\n", - " weave.legacy.panels.BoardPanel(\n", - " # TODO: have to compute all columns we want here, there's no way to\n", - " # tell js or python to do auto-columns behavior.\n", - " lambda all_spans: weave.legacy.panels.Table(\n", - " all_spans,\n", - " columns=[\n", - " weave.legacy.panels.TableColumn(\n", - " lambda row: row['trace_id'],\n", - " groupby=True,\n", - " ),\n", - " weave.legacy.panels.TableColumn(\n", - " lambda row: row.count(),\n", - " name='span count'\n", - " ),\n", - " weave.legacy.panels.TableColumn(\n", - " # TODO: not exact, we need to add duration to get end time, but we don't have an add\n", - " # for 2 vectors yet.\n", - " lambda row: (row['start_time_ms'].max() - row['start_time_ms'].min()) / 1000,\n", - " name='duration_s'\n", - " )\n", - " ]\n", - " ),\n", - " id='traces',\n", - " layout=weave.legacy.panels.BoardPanelLayout(x=0, y=0, w=24, h=6)\n", - " ),\n", - " weave.legacy.panels.BoardPanel(\n", - " # TODO: have to compute all columns we want here, there's no way to\n", - " # tell js or python to do auto-columns behavior.\n", - " lambda all_spans: weave.legacy.panels.Table(\n", - " all_spans,\n", - " columns=[\n", - " weave.legacy.panels.TableColumn(\n", - " lambda row: row['name'],\n", - " groupby=True,\n", - " ),\n", - " lambda row: row.count(),\n", - " weave.legacy.panels.TableColumn(\n", - " # TODO: not exact, we need to add duration to get end time, but we don't have an add\n", - " # for 2 vectors yet.\n", - " lambda row: (row['start_time_ms'].max() - row['start_time_ms'].min()) / 1000,\n", - " name='duration_s'\n", - " )\n", - " ]\n", - " ),\n", - " id='spans',\n", - " layout=weave.legacy.panels.BoardPanelLayout(x=0, y=0, w=24, h=6)\n", - " ),\n", - " weave.legacy.panels.BoardPanel(\n", - " lambda selected_spans: weave.legacy.panels.Trace(selected_spans),\n", - " layout=weave.legacy.panels.BoardPanelLayout(x=0, y=6, w=24, h=6)\n", - " )\n", - " ]\n", - ")\n", - "board" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/experimental/skip_test/weave_scifi.ipynb b/examples/experimental/skip_test/weave_scifi.ipynb deleted file mode 100644 index d6144ba3fff..00000000000 --- a/examples/experimental/skip_test/weave_scifi.ipynb +++ /dev/null @@ -1,80 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "3c4790a9", - "metadata": {}, - "outputs": [], - "source": [ - "import weave\n", - "from weave import weave_internal\n", - "from weave.legacy.ecosystem.all import langchain\n", - "weave.use_frontend_devmode()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "2e9fe6dd", - "metadata": {}, - "outputs": [], - "source": [ - "weave.legacy.panels.Board(\n", - " vars={\n", - " \"gpt\": langchain.chat_openai(\"gpt-3.5-turbo\", 0.7),\n", - " \"viz_style\" : [\"vintage art deco travel poster\",\n", - " \"futuristic science fiction movie\",\n", - " \"cute retro space anime\"],\n", - " \"viz_id\" : 0,\n", - " \"num_rows\": 2,\n", - " \"ml_terms\" : [\"models\", \"datasets\"],\n", - " \"prompts\": \n", - " lambda gpt, num_rows, ml_terms:\n", - " weave.legacy.ops.range(0, num_rows, 1).map(\n", - " lambda row:\n", - " gpt.predict(weave_internal.const(\"You are an expert AGI architect describing the most powerful and flexible user interface for working with \")\n", - " + ml_terms[row] + \"in machine learning and AI. The three most important features for this are:\")\n", - " \n", - " ),\n", - " \"dream_view\":\n", - " lambda prompts, num_rows, viz_style, viz_id:\n", - " weave.legacy.ops.range(0, num_rows, 1).map(\n", - " lambda row: weave_internal.const(\"a \") + viz_style[viz_id] + \" of a \" + prompts[row]).stable_diffusion(),\n", - " },\n", - " panels=[\n", - " weave.legacy.panels.BoardPanel(\n", - " lambda prompts: weave.legacy.panels.Table(prompts, columns=[lambda col: col]),\n", - " layout=weave.legacy.panels.BoardPanelLayout(x=0, y=0, w=12, h=16)\n", - " ),\n", - " weave.legacy.panels.BoardPanel(\n", - " lambda dream_view: weave.legacy.panels.Table(dream_view, columns=[lambda col: col]),\n", - " layout=weave.legacy.panels.BoardPanelLayout(x=12, y=0, w=12, h=16)\n", - " ),\n", - " ]\n", - ")" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.9" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/experimental/spacy_demo.ipynb b/examples/experimental/spacy_demo.ipynb deleted file mode 100644 index b5340e51112..00000000000 --- a/examples/experimental/spacy_demo.ipynb +++ /dev/null @@ -1,52 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "cd35f2be", - "metadata": {}, - "outputs": [], - "source": [ - "# Run the following installation if you have not already\n", - "#!python -m spacy download en_core_web_sm" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ede77c85", - "metadata": {}, - "outputs": [], - "source": [ - "from weave.legacy.ecosystem.spacy import spacy\n", - "\n", - "text = \"\"\"Welcome to the spaCy Demo.\\\n", - "We currently support the small English language model, \\\n", - "and will add support for different types in the near future!\"\"\"\n", - "\n", - "spacy(text)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.9" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/experimental/table_data_passing.ipynb b/examples/experimental/table_data_passing.ipynb deleted file mode 100644 index de674e689ff..00000000000 --- a/examples/experimental/table_data_passing.ipynb +++ /dev/null @@ -1,82 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "c09f7649", - "metadata": {}, - "outputs": [], - "source": [ - "import weave\n", - "import weave.legacy.panels\n", - "# Weave package now defaults to eager mode, but lazy mode required for this example notebook for now.\n", - "weave.use_lazy_execution()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a6b83817", - "metadata": {}, - "outputs": [], - "source": [ - "panel = weave.legacy.panels.Group(\n", - " items={\n", - " \"table\": weave.legacy.panels.Table(weave.legacy.ops.range(1, 100, 1), \n", - " columns=[\n", - " lambda row: row,\n", - " lambda row: row ** 2,\n", - " ],\n", - " ),\n", - " \"all_rows\": lambda table: weave.legacy.panels.Plot(table.all_rows(),\n", - " x=lambda row: row['c_0'],\n", - " y=lambda row: row['c_1']\n", - " ),\n", - " \"derived\": lambda table: weave.legacy.panels.Group(\n", - " preferHorizontal=True,\n", - " layoutMode=\"horizontal\",\n", - " items={\n", - " \"rows\": weave.legacy.panels.Group(\n", - " items={\n", - " \"pinned_rows\": weave.legacy.panels.Plot(table.pinned_rows(),\n", - " x=lambda row: row['c_0'],\n", - " y=lambda row: row['c_1']\n", - " ),\n", - " \"active_row\": table.active_row(),\n", - " }\n", - " ),\n", - " \"data\": weave.legacy.panels.Group(\n", - " items={\n", - " \"pinned_data\": table.pinned_data(),\n", - " \"active_data\": table.active_data(),\n", - " }\n", - " )\n", - " }) \n", - " })\n", - "\n", - "panel" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/experimental/tag_search.ipynb b/examples/experimental/tag_search.ipynb deleted file mode 100644 index edd83696e7e..00000000000 --- a/examples/experimental/tag_search.ipynb +++ /dev/null @@ -1,110 +0,0 @@ -{ - "cells": [ - { - "attachments": {}, - "cell_type": "markdown", - "id": "550a2af8", - "metadata": {}, - "source": [ - "## An example implementation of tag search\n", - "\n", - "This shows how to achieve a UI for tag search.\n", - "\n", - "Its not ideal in these ways:\n", - "- we're missing an intersection op.\n", - "- we'll need to be sure the pattern vectorizes\n", - "- list of tag strings may not be the best representation for performance at scale\n", - "- we don't have a nice way to display list of strings as tags\n", - "- no centralized tag color control\n", - "- you should be able to click on a tag and filter the table down to that tag\n", - "- varbar construction of controls is still pretty odd\n", - "\n", - "But it works!\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9c690522", - "metadata": {}, - "outputs": [], - "source": [ - "import weave\n", - "from weave import weave_internal" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "cd9ad6f9", - "metadata": {}, - "outputs": [], - "source": [ - "data = weave.save([\n", - " {'x': 15, 'tags': ['a', 'b']},\n", - " {'x': 23.9, 'tags': ['b', 'c']},\n", - " {'x': -8, 'tags': ['a']}\n", - "], 'taggeddata')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "34fb2bba", - "metadata": {}, - "outputs": [], - "source": [ - "weave.legacy.panels.Board(\n", - " vars={\n", - " # The strange that we need to use two variables for structuring our tag picker and its value.\n", - " 'filter_tags': [],\n", - " 'tag_picker': lambda filter_tags: weave.legacy.panels.SelectEditor(\n", - " filter_tags,\n", - " choices=weave_internal.const(['a', 'b', 'c'])\n", - " ),\n", - " 'data': data,\n", - " 'filtered_data': lambda data, filter_tags: data.filter(\n", - " # We don't currently have an intersection op, so we have to write the logic for it here.\n", - " # This will probably not vectorize currently, so will be slower on large amounts of data.\n", - " lambda row: weave.legacy.ops.List.concat(weave.legacy.ops.make_list(a=row['tags'], b=filter_tags)).unique().count()\n", - " < (row['tags'].count() + filter_tags.count())\n", - " )\n", - " },\n", - " panels=[\n", - " weave.legacy.panels.BoardPanel(\n", - " lambda filtered_data: weave.legacy.panels.Table(\n", - " filtered_data,\n", - " columns=[\n", - " lambda row: row['x'],\n", - " # Use json_dumps to get a better view for now.\n", - " lambda row: row['tags'].json_dumps()\n", - " ]\n", - " )\n", - " )\n", - " ]\n", - ")" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/get_started.ipynb b/examples/get_started.ipynb deleted file mode 100644 index b8b5a4f101a..00000000000 --- a/examples/get_started.ipynb +++ /dev/null @@ -1,301 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "81c835f8", - "metadata": {}, - "source": [ - "# Get Started building with Weave\n", - "\n", - "In this tutorial, you will learn how to use Weave to build an interactive dashboard to help you explore, understand, and evaluate machine learning models.\n", - "\n", - "After this tutorials, you'll know how to build a dashboard to:\n", - "\n", - "- explore inputs, outputs & labels of your models\n", - "- calculate and plot metrics about your models\n", - "- interactively build live, connected plots to drill deeper into your data\n", - "- publish sharable interactive dashboards to enable colleagues to build on your work\n", - "\n", - "# Hello, World\n", - "\n", - "First, install `weave`:\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "e45d5578", - "metadata": {}, - "outputs": [], - "source": [ - "!pip install weave -qqq" - ] - }, - { - "cell_type": "markdown", - "id": "56c791d0", - "metadata": {}, - "source": [ - "Next, we'll use Weave to explore the Iris flower dataset.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b2f76045", - "metadata": {}, - "outputs": [], - "source": [ - "from sklearn.datasets import load_iris\n", - "\n", - "def get_iris_dataset():\n", - " iris = load_iris(as_frame=True)\n", - " data = iris['data']\n", - " data['target_id'] = iris['target']\n", - " data['target_name'] = iris['target_names'][iris['target']]\n", - "\n", - " def clean_column_name(c):\n", - " return c.replace(\" (cm)\", \"\").replace(\" \", \"_\")\n", - " data.columns = [clean_column_name(c) for c in data.columns]\n", - " return data\n", - "\n", - "iris_df = get_iris_dataset()" - ] - }, - { - "cell_type": "markdown", - "id": "ae1d5797", - "metadata": {}, - "source": [ - "Now that we have a dataframe, we can display it in an interactive table by calling `weave.show`.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "84f1a891", - "metadata": {}, - "outputs": [], - "source": [ - "import weave\n", - "from weave.legacy.show import show\n", - "show(iris_df)" - ] - }, - { - "cell_type": "markdown", - "id": "091192b7-ede4-423f-a78d-b06a9b877bab", - "metadata": {}, - "source": [ - "This is already a lot more powerful that the static visualization of a pandas `DataFrame` because you can page through it——and we're just getting started.\n", - "\n", - "# Building plots\n", - "\n", - "Weave can dynamically create visualizations of your data.\n", - "\n", - "- At the top of the panel, click `Table`\n", - "- You should see a dropdown, click `Plot`\n", - "\n", - "You should now see a plot of `sepal_width` versus `sepal_length`, colored by `target_name`. Weave's visualization engine uses the type of each column to choose a reasonable way to display your data. You can edit this plot by clicking the pencil icon ✏️ in the top right of the panel to open the panel editor.\n", - "\n", - "Note: You can build these plots interactively using the UI or define them programmatically in Python. In this tutorial, we'll focus on building these dashboards using the UI and keep programmatic dashboard building for later tutorials.\n" - ] - }, - { - "cell_type": "markdown", - "id": "9866379b", - "metadata": {}, - "source": [ - "!['image'](https://raw.githubusercontent.com/wandb/weave/6936cd8233f382ae3fd165480dce5832c06c2453/docs/assets/get_started_0.png)\n" - ] - }, - { - "cell_type": "markdown", - "id": "bd62be8e", - "metadata": {}, - "source": [ - "## Exercise\n", - "\n", - "- Try changing the X and Y axes\n", - "- Try changing the color\n", - "\n", - "Tip: `row[\"\"]` is a **Weave Expression**. You can edit the column name inside the quotes to show the relevant column from your data.\n", - "\n", - "# Exploring Tables\n", - "\n", - "Let's display our data as a `Table` again:\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "886c7c49-e7f8-4651-8a6a-b2a08d417dda", - "metadata": {}, - "outputs": [], - "source": [ - "show(iris_df)" - ] - }, - { - "cell_type": "markdown", - "id": "6d12837d", - "metadata": {}, - "source": [ - "You can add, remove and sort each column by using the three dots button ⋮ beside the column name.\n", - "\n", - "## Exercise\n", - "\n", - "- Try sorting by the `sepal_length` column\n", - "- Try removing the `target_id` column\n", - "\n", - "## Filtering\n", - "\n", - "You can filter tables using the funnel icon ▼ on the top left.\n", - "\n", - "For example, to filter the data to only display the `setosa` flowers:\n", - "\n", - "- Click the filter icon\n", - "- Add the expression `row[\"target_name\"] == \"setosa\"`\n", - "\n", - "# Transforming data\n", - "\n", - "When analyzing models, you often need to use your data to calculate metrics or statistics to get summaries for comparison.\n", - "\n", - "To enable this, Weave has built-in operations, or **Weave Ops**, to transform data. You can also configure the data displayed in each column as we did with the plot.\n", - "\n", - "For example, to display the sum of each examples' `sepal_width` and `sepal_length`:\n", - "\n", - "- Click a column to edit (e.g. `sepal_length`)\n", - "- Change the Cell expression to `row[\"sepal_width\"] + row[\"sepal_length\"]`\n", - "- Click anywhere outside the popover to apply the change\n", - "\n", - "# Group by columns to compare splits and calculate summary statistics\n", - "\n", - "Comparing performance across different data splits is vital to uncovering biases and edge cases. This is fast and visual in Weave using the **Group by** operation.\n", - "\n", - "For example, if we want to calculate summary statistics of each type of flower in our dataset, we can group by the `target_name` column.\n", - "\n", - "- Click the three dots button ⋮ on the `target_name` column\n", - "- Click `Group by`\n", - "\n", - "We now see that Weave has grouped our data by the `target_name` and made plots for each column. We can now use different operations on these columns because they're now _lists_ rather than individual elements.\n", - "\n", - "For example, we can calculate the average of each column by changing the cell expression to be `row[\"\"].avg` e.g. `row[\"sepal_length\"].avg`.\n", - "\n", - "## Exercise\n", - "\n", - "- Try calculating the maximum of each column\n" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "id": "0954b735", - "metadata": {}, - "source": [ - "!['image'](https://raw.githubusercontent.com/wandb/weave/4885095070704922189ba4d761029821c4e5da42/docs/assets/get_started_1.png)\n" - ] - }, - { - "cell_type": "markdown", - "id": "60acbe66", - "metadata": {}, - "source": [ - "## Drilldown with connected plots\n", - "\n", - "Finally, once you have found a split of data that is interesting, you often want to view individual elements of that data to better understand it. Weave makes it easy to connect multiple visualizations so that you can drilldown into selected data.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "531013bb", - "metadata": {}, - "outputs": [], - "source": [ - "show(iris_df)" - ] - }, - { - "cell_type": "markdown", - "id": "2519db1c", - "metadata": {}, - "source": [ - "!['image'](https://raw.githubusercontent.com/wandb/weave/4885095070704922189ba4d761029821c4e5da42/docs/assets/get_started_2.png)\n" - ] - }, - { - "cell_type": "markdown", - "id": "ddad55c3", - "metadata": {}, - "source": [ - "For example, to select data from a plot and display it in a table:\n", - "\n", - "- Add a new panel from the drawer menu on the right (1)\n", - "- Using the panel name (2), refer to the `.selected_data` from the first panel in the expression editor (3)\n", - "- Use the selection tool (4) to display that data in the drilldown table\n", - "\n", - "Tip: You can also connect `Tables` to other plots. To select a row on a table, click the row number. To use that selection, use `.active_data`.\n", - "\n", - "# Publish your dashboard and share it\n", - "\n", - "Once you have something you're happy with, you can click the arrow ↗️ from the drawer menu on the right to open it in a new tab. From there, you can click \"Publish board\" to send your creation to the cloud and share it with the world. You can also use this area to add new panels and update your dashboard.\n", - "\n", - "# What's next?\n", - "\n", - "With what you've learned, try build something on your own data. Weave works with lots of different types of data and is performant at large scales!\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "e2011a09", - "metadata": {}, - "outputs": [], - "source": [ - "import numpy\n", - "from PIL import Image\n", - "\n", - "def random_image():\n", - " imarray = numpy.random.rand(100,100,3) * 255\n", - " return Image.fromarray(imarray.astype('uint8')).convert('RGBA')\n", - "\n", - "show([{'image_col': random_image(), 'text_col': 'Hello'}, \n", - " {'image_col': random_image(), 'text_col': 'World'}]*1000)" - ] - }, - { - "cell_type": "markdown", - "id": "6db51292", - "metadata": {}, - "source": [ - "We're heads down focusing on adding templates and improving our documentation. Weave is open source, and we're very excited to merge your contributions. If you have feedback, feature requests, or bugs, you can open an issue on the Weave Github repository.\n", - "\n", - "## Not finished learning?\n", - "\n", - "Check out our [examples](https://github.com/wandb/weave/tree/master/examples) to see more tutorials, a gallery of dashboards, and reference guides to different components.\n" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.11" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/prompts/llm_monitoring/README.md b/examples/prompts/llm_monitoring/README.md index fcb12dd331b..7636594f80f 100644 --- a/examples/prompts/llm_monitoring/README.md +++ b/examples/prompts/llm_monitoring/README.md @@ -20,7 +20,7 @@ There are two main ways to authenticate OpenAI API calls so you can view them in ### OpenAI API 1. Set your OPENAI_API_KEY in your environment/script/notebook (e.g. via `os.environ["OPENAI_API_KEY"]`). -2. Import openai as follows: `from weave.legacy.monitoring import openai`. +2. Import openai as follows: `from weave.legacy.weave.monitoring import openai`. 3. Make calls via the OpenAI SDK as usual. See details and create an interactive board in the [OpenAI monitoring notebook](./openai_client_quickstart.ipynb). diff --git a/examples/prompts/llm_monitoring/openai_client_quickstart.ipynb b/examples/prompts/llm_monitoring/openai_client_quickstart.ipynb index f9b39f1af13..f0fc64bdceb 100644 --- a/examples/prompts/llm_monitoring/openai_client_quickstart.ipynb +++ b/examples/prompts/llm_monitoring/openai_client_quickstart.ipynb @@ -135,7 +135,7 @@ "metadata": {}, "outputs": [], "source": [ - "from weave.legacy.monitoring import openai, init_monitor\n", + "from weave.legacy.weave.monitoring import openai, init_monitor\n", "m = init_monitor(f\"{WB_ENTITY}/{WB_PROJECT}/{STREAM_NAME}\")\n", "\n", "# specifying a single model for simplicity\n", @@ -245,7 +245,7 @@ "metadata": {}, "outputs": [], "source": [ - "from weave.legacy.monitoring.openai import message_from_stream\n", + "from weave.legacy.weave.monitoring.openai import message_from_stream\n", "r = openai.ChatCompletion.create(model=OPENAI_MODEL, messages=[\n", " {\"role\": \"system\", \"content\": \"You are a robot and only speak in robot, like beep bloop bop.\"},\n", " {\"role\": \"user\", \"content\": \"Tell me a 50-word story.\"},\n", diff --git a/examples/prompts/llm_monitoring/openai_proxy_quickstart_enterprise_mode.ipynb b/examples/prompts/llm_monitoring/openai_proxy_quickstart_enterprise_mode.ipynb index 57d8c1eaa96..b2c1cda36cb 100644 --- a/examples/prompts/llm_monitoring/openai_proxy_quickstart_enterprise_mode.ipynb +++ b/examples/prompts/llm_monitoring/openai_proxy_quickstart_enterprise_mode.ipynb @@ -251,8 +251,8 @@ "outputs": [], "source": [ "import openai\n", - "from weave.legacy.monitoring import openai as openaimon\n", - "from weave.legacy.monitoring import init_monitor\n", + "from weave.legacy.weave.monitoring import openai as openaimon\n", + "from weave.legacy.weave.monitoring import init_monitor\n", "\n", "m = init_monitor(f\"{WB_ENTITY}/{WB_PROJECT}/{STREAM_NAME}\")\n", "\n", diff --git a/examples/prompts/trace_debugging/dev/synthetic_trace_data.ipynb b/examples/prompts/trace_debugging/dev/synthetic_trace_data.ipynb index 3834e6ea2a5..be0eae16ac5 100644 --- a/examples/prompts/trace_debugging/dev/synthetic_trace_data.ipynb +++ b/examples/prompts/trace_debugging/dev/synthetic_trace_data.ipynb @@ -39,7 +39,7 @@ "outputs": [], "source": [ "import weave\n", - "from weave.legacy.monitoring import StreamTable\n", + "from weave.legacy.weave.monitoring import StreamTable\n", "from weave.stream_data_interfaces import TraceSpanDict" ] }, diff --git a/examples/prompts/trace_debugging/trace_quickstart_decorator.ipynb b/examples/prompts/trace_debugging/trace_quickstart_decorator.ipynb index d692e72734b..bc0dd40d255 100644 --- a/examples/prompts/trace_debugging/trace_quickstart_decorator.ipynb +++ b/examples/prompts/trace_debugging/trace_quickstart_decorator.ipynb @@ -46,7 +46,7 @@ "source": [ "import weave\n", "import time\n", - "from weave.legacy.monitoring import init_monitor" + "from weave.legacy.weave.monitoring import init_monitor" ] }, { diff --git a/examples/prompts/trace_debugging/trace_quickstart_langchain.ipynb b/examples/prompts/trace_debugging/trace_quickstart_langchain.ipynb index 4e5080d95a1..72c3257f562 100644 --- a/examples/prompts/trace_debugging/trace_quickstart_langchain.ipynb +++ b/examples/prompts/trace_debugging/trace_quickstart_langchain.ipynb @@ -75,7 +75,7 @@ "source": [ "import langchain\n", "import weave\n", - "from weave.legacy.monitoring.langchain import WeaveTracer" + "from weave.legacy.weave.monitoring.langchain import WeaveTracer" ] }, { diff --git a/examples/reference/WB_API.ipynb b/examples/reference/WB_API.ipynb deleted file mode 100644 index 04747e092f6..00000000000 --- a/examples/reference/WB_API.ipynb +++ /dev/null @@ -1,89 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "eae3e8dd", - "metadata": {}, - "outputs": [], - "source": [ - "import weave\n", - "import weave.legacy.panels\n", - "import weave.legacy.ops\n", - "from weave.legacy.show import show" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "887d88a4", - "metadata": {}, - "outputs": [], - "source": [ - "project = weave.legacy.ops.project('stacey', \"mendeleev\")\n", - "#project" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b7705fb7", - "metadata": {}, - "outputs": [], - "source": [ - "panel = weave.legacy.panels.Table(weave.legacy.ops.project('stacey', \"mendeleev\").artifactType('test_results').artifacts(),\n", - " columns=[\n", - " lambda art: weave.legacy.ops.artifact_collection_ops.artifact_name(art), # hack to get around shadowed `name` op\n", - " lambda art: art.versions().count()\n", - " ])\n", - "show(panel)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "58b26e21", - "metadata": {}, - "outputs": [], - "source": [ - "weave.legacy.ops.project('stacey', \"mendeleev\").artifactType('test_results').artifacts()[0].versions()[0].file('test_results.table.json')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "debbb5d4", - "metadata": {}, - "outputs": [], - "source": [ - "weave.legacy.ops.project('shawn', \"dsviz-simple-tables\").artifactType('dataset').artifacts()[0].versions()[5].file('my-table.table.json')" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.7" - }, - "vscode": { - "interpreter": { - "hash": "cdaa5b4f919dd2bcc1e1b23efc336c7178fbd1227e862ece13b7d448295c4271" - } - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/reference/confusion_matrix.ipynb b/examples/reference/confusion_matrix.ipynb deleted file mode 100644 index 415c75a743a..00000000000 --- a/examples/reference/confusion_matrix.ipynb +++ /dev/null @@ -1,234 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "562cca2e-fd23-4725-9c04-416fde40aadc", - "metadata": {}, - "source": [ - "# Create an Interactive Confusion Matrix in Weave\n", - "\n", - "In this tutorial, we will set up an interactive confusion matrix Weave panel using Weave expressions and Python):\n", - "* pick an existing W&B Table of predictions\n", - "* load this data into Weave locally\n", - "* programmatically configure two Weave Panels: a Facet panel for the confusion matrix grid and an active selection panel to show the details of items in that cell of the confusion matrix\n", - "\n", - "\n", - "\n", - "# Step 0: Setup\n", - "\n", - "Install dependencies and login to W&B to save & publish your work." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8544cf72", - "metadata": {}, - "outputs": [], - "source": [ - "# if not already installed\n", - "!pip install -qqq weave" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5b86972f-d8d7-4f5a-bf5a-9a6be887978c", - "metadata": {}, - "outputs": [], - "source": [ - "import wandb\n", - "wandb.login()\n", - "import weave\n", - "import weave.legacy.panels" - ] - }, - { - "cell_type": "markdown", - "id": "b35a8e31-3f51-43a9-bf89-543ed6fe62e8", - "metadata": {}, - "source": [ - "Use our public example or substitute your own W&B Tables by replacing the fields below. You can find the relevant information by navigating to a project in W&B, clicking on the \"Artifacts\" tab, and browsing using the sidebar until you find the artifact type and corresponding table file of interest (e.g. [this one used in the example](https://wandb.ai/stacey/mendeleev/artifacts/test_results/test_res_1fwmcd3q/v0/files/test_results.table.json)).\n", - "\n", - "**Note:** the index '0' after `artifacts()` and `versions()` will take the first element in those lists." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "01183f54-efa6-405a-be6a-142f75540913", - "metadata": {}, - "outputs": [], - "source": [ - "ENTITY = \"stacey\"\n", - "PROJECT = \"mendeleev\"\n", - "ARTIFACT_TYPE = \"test_results\"\n", - "TABLE_FILE = \"test_results.table.json\"" - ] - }, - { - "cell_type": "markdown", - "id": "2c910260-a5c0-4c99-bd00-1b9b95ddde54", - "metadata": {}, - "source": [ - "# Step 1: Compose Weave expression to load W&B Table\n", - "\n", - "Navigate the W&B object tree programmatically with Weave\n", - "* start with the specified W&B project\n", - "* fetch all artifacts of the specified type and take the first artifact\n", - "* fetch all the versions of that artifact and take the first one\n", - "* load the specified W&B Table file associated with that artifact version\n", - "* convert that file into a Weave table (with `.table.rows()`)\n", - "\n", - "The second cell below will display an interactive Weave Panel of the corresponding Table. You can interact with this panel in the notebook as you would with the underlying W&B Table in the cloud UI: sort, filter, group, etc. You can also expand the panel to open it in a Weave Board in order to iterate further/save and publish your work." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "7f5447d2", - "metadata": {}, - "outputs": [], - "source": [ - "predictions = (weave.legacy.ops.project(ENTITY, PROJECT)\n", - " .artifactType(ARTIFACT_TYPE)\n", - " .artifacts()[0]\n", - " .versions()[0]\n", - " .file(TABLE_FILE)\n", - " .table()\n", - " .rows())" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "e414990d", - "metadata": {}, - "outputs": [], - "source": [ - "predictions" - ] - }, - { - "cell_type": "markdown", - "id": "9ee223ef-1a97-46f6-9933-24cdb78d2488", - "metadata": {}, - "source": [ - "# Step 2: Configure a Weave Panel Group in code\n", - "\n", - "We write a function to create a group of panels in Weave:\n", - "* Group panel: this is a container to arrange a set of panels — in our case, in equal sizes horizontally\n", - "* Facet panel: this is the main 2D grid of the confusion matrix, showing the `truth` column (correct label) on the x-axis and the `guess` column (model's prediction) on the y-axis\n", - "* each cell of the Facet Panel is again a Group with two layers of panels: 1) a Number panel showing the total number of rows/model predictions in that cell of the confusion matrix, and 2) a background Color panel with the count from 1) rescaled on a blue hue, so the more saturated the blue, the more items in that cell\n", - "* a final Table panel to show the full rows/details of any selected cell from the main Facet panel." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a940dab4", - "metadata": {}, - "outputs": [], - "source": [ - "def confusion_matrix(table_data):\n", - " conf_mat = weave.legacy.panels.Group(\n", - " equalSize=True,\n", - " preferHorizontal=True,\n", - " items={\n", - " 'confusion': weave.legacy.panels.Facet(\n", - " table_data,\n", - " x=lambda row: row[\"truth\"],\n", - " y=lambda row: row[\"guess\"],\n", - " select=lambda row: weave.legacy.panels.Group(\n", - " layoutMode='layer',\n", - " items={\n", - " 'count': row.count(),\n", - " 'color': weave.legacy.panels.Color(row.count() / 50)\n", - " }\n", - " )\n", - " ),\n", - " 'selected': lambda confusion: confusion.selected()\n", - " }\n", - " )\n", - " return conf_mat" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ccf9a5a9-07b0-4967-a5ee-9255f9660ee3", - "metadata": {}, - "outputs": [], - "source": [ - "confusion_matrix(predictions)" - ] - }, - { - "cell_type": "markdown", - "id": "050f6119-7933-4084-9a9c-edb829c6c075", - "metadata": {}, - "source": [ - "# Step 3: Filter for incorrect predictions\n", - "\n", - "We can add a filter predicate to the Weave expression to select only the rows where the model's prediction doesn't match the correct label. Note that the highest-count / most-blue squares are absent in this version.\n", - "\n", - "Try opening this panel as a Weave Board by hovering over the right side and clicking \"Open in new tab\". You can explore more cells, add more panels, and share your work. Let us know if you find any interesting mistakes or patterns—for example, the mammal confused for an arachnid :)\n", - "\n", - "" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "75d50c33-9712-4ffe-9971-5552d32648bc", - "metadata": {}, - "outputs": [], - "source": [ - "mistakes_only = predictions.filter(lambda row: row[\"truth\"] != row[\"guess\"])\n", - "confusion_matrix(mistakes_only)" - ] - }, - { - "cell_type": "markdown", - "id": "9e4195fe-09cb-48e8-99a0-e01acf75e6c2", - "metadata": {}, - "source": [ - "# Bonus: Make a confusion matrix from the UI only\n", - "\n", - "To create a confusion matrix from the UI only:\n", - "1. Start with a Weave Table object saved with `weave.save()` and loaded into a panel with an expression of the form `get(\"wandb-artifact:///your_entity/your_project/obj:0123456789abcde/obj\")`.\n", - "2. Click the pencil \"Edit\" icon on the panel to open the panel config menu.\n", - "3. Change the Panel type to \"Facet\"—this is the main confusion matrix layout.\n", - "4. Set the X and Y axes for the intended confusion matrix, e.g to `row[\"truth\"]` and `row[\"guess\"]` for this example.\n", - "5. In the lower \"Properties\" section, change the child panel type from \"Expression\" to \"Group\", then change the layout from \"vertical\" to \"layer\" and select \"Add child\"—each child is a cell of the confusion matrix which \"groups\" or \"layers\" the count of items in that cell and the corresponding background color of the cell.\n", - "6. The \"Input\" of one child can be the count: enter `row.count` as the Weave expression. When you hit \"Run\", \"Panel type\" should update to \"Number\" and you should see numbers render in the confusion matrix panel.\n", - "7. Click \"Add Child\" and optionally add the cell background. Change \"Panel type\" to \"Color\" and enter `row.count / N` as the \"Input\", where N is some normalizing constant to scale your values from 0 for white to ~1.5 for the highest-saturation blue.\n", - "\n", - "Your settings menu should look something like this:\n", - "\n", - "" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/reference/control/Object Picker.ipynb b/examples/reference/control/Object Picker.ipynb deleted file mode 100644 index 779f9eac812..00000000000 --- a/examples/reference/control/Object Picker.ipynb +++ /dev/null @@ -1,121 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "f9712d25", - "metadata": {}, - "outputs": [], - "source": [ - "import weave\n", - "import weave.legacy.panels" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "2a60bff3", - "metadata": {}, - "outputs": [], - "source": [ - "ints = weave.save([1, 2, 3])" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "2185cbbc", - "metadata": {}, - "outputs": [], - "source": [ - "panel = weave.legacy.panels.Group(\n", - " showExpressions=False,\n", - " items={\n", - " \"a\": weave.legacy.panels.ObjectPicker(ints, label=\"a\"),\n", - " \"b\": weave.legacy.panels.ObjectPicker(ints, label=\"b\"),\n", - " \"panel\": lambda a, b: a.config.choice + b.config.choice\n", - " })\n", - "panel" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a0f2f998", - "metadata": {}, - "outputs": [], - "source": [ - "# A more complex example with sidebar\n", - "\n", - "# ints = weave.legacy.ops.objects(weave.types.Int(), 'latest', 9)\n", - "# weave.use(ints[0])\n", - "ints = weave.save([1, 2, 3], name='ints:latest')\n", - "panel = weave.legacy.panels.Group(\n", - " preferHorizontal=True,\n", - " equalSize=True,\n", - " items={\n", - " \"sidebar\": weave.legacy.panels.Group(\n", - " items={\n", - " \"a\": weave.legacy.panels.ObjectPicker(ints, label=\"A\"),\n", - " \"b\": weave.legacy.panels.ObjectPicker(ints, label=\"B\")\n", - " }\n", - " ),\n", - " \"main\": lambda sidebar: weave.legacy.panels.Group(\n", - " items={\n", - " \"choice_values\": weave.legacy.panels.Group(\n", - " preferHorizontal=True,\n", - " style=\"height:150px\",\n", - " items={\n", - " 'choice_a': weave.legacy.panels.LabeledItem(\n", - " label=\"A\",\n", - " item=sidebar.config.items['a'].config.choice.execute()\n", - " ),\n", - " 'choice_b': weave.legacy.panels.LabeledItem(\n", - " label=\"B\",\n", - " item=sidebar.config.items['b'].config.choice.execute()\n", - " )\n", - " }\n", - " ),\n", - " \"aggregates\": weave.legacy.panels.Group(\n", - " preferHorizontal=True,\n", - " style=\"height:150px\",\n", - " items={\n", - " 'sum': weave.legacy.panels.LabeledItem(\n", - " label='sum',\n", - " item=sidebar.config.items['a'].config.choice + sidebar.config.items['b'].config.choice\n", - " ),\n", - " 'product': weave.legacy.panels.LabeledItem(\n", - " label='product',\n", - " item=sidebar.config.items['a'].config.choice * sidebar.config.items['b'].config.choice\n", - " )\n", - " }\n", - " )\n", - " }\n", - " )\n", - " })\n", - "panel" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/reference/control/Slider.ipynb b/examples/reference/control/Slider.ipynb deleted file mode 100644 index 434f22dddf9..00000000000 --- a/examples/reference/control/Slider.ipynb +++ /dev/null @@ -1,75 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "83fee798", - "metadata": {}, - "outputs": [], - "source": [ - "import weave\n", - "from weave.legacy import panels" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "6c85e3b7", - "metadata": {}, - "outputs": [], - "source": [ - "# Uncontrolled: panel creates its own state slot\n", - "panel = panels.Group(\n", - " items={\n", - " \"my_slider\": panels.Slider(),\n", - " \"val\": lambda my_slider: my_slider.value()\n", - " })\n", - "panel" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "0dd8dd51", - "metadata": {}, - "outputs": [], - "source": [ - "# Controlled: state is external to panel\n", - "\n", - "panel = panels.Group(\n", - " vars={'slider_val': 5},\n", - " items={\n", - " 'state_value': lambda slider_val: slider_val,\n", - " 'slider': lambda slider_val: panels.Group(\n", - " items={\n", - " \"my_slider\": panels.Slider(slider_val),\n", - " \"val\": lambda my_slider: my_slider.value(),\n", - " \"my_slider2\": panels.Slider(slider_val)\n", - " })\n", - " })\n", - "panel" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/reference/create_plots_ui_guide.ipynb b/examples/reference/create_plots_ui_guide.ipynb deleted file mode 100644 index 468507e3b2e..00000000000 --- a/examples/reference/create_plots_ui_guide.ipynb +++ /dev/null @@ -1,330 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "1305d15c-9677-4f65-8cd8-338d1cb7aa82", - "metadata": {}, - "source": [ - "# Weave Plot: Interactively visualize your data\n", - "\n", - "In this tutorial, we will visualize data with the **Weave Plot Panel**:\n", - "* load a Pandas DataFrame or other tabular data into Weave \n", - "* visualize your data as a 2D plot of (x, y) points with **Weave Plot**\n", - "* customize the plot to filter or annotate the data\n", - "\n", - "We use an example dataset of [notable machine learning models](https://docs.google.com/spreadsheets/d/1AAIebjNsnJj_uKALHbXNfn3_YsT6sHXtCU0q7OIPuc4/edit#gid=0) to get started—you can try your own CSV file, Pandas DataFrame, or any public Google Sheets url.\n", - "\n", - "# 0: Setup\n", - "Import dependencies & login to W&B to save your work and share any visualizations you create." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "43053b45-eed1-445c-8bbd-05f113302060", - "metadata": {}, - "outputs": [], - "source": [ - "!pip install -qqq weave\n", - "import pandas as pd" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "0d5645e7", - "metadata": {}, - "outputs": [], - "source": [ - "import wandb\n", - "wandb.login()\n", - "import weave\n", - "from weave.legacy.show import show" - ] - }, - { - "cell_type": "markdown", - "id": "cb79fff5-f4fd-45db-a48e-c8581e0e5ad1", - "metadata": {}, - "source": [ - "# 1: Load your data as an interactive table\n", - "\n", - "Load a sample dataset of notable ML publications (560 rows, 33 columns as of Aug 8 2023). Feel free to edit these variables to\n", - "* load in your own Pandas DataFrame\n", - "* load a different CSV file\n", - "* modify the Google Sheets URL and sheet id to work with a different public spreadsheet" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8cf0eddd", - "metadata": {}, - "outputs": [], - "source": [ - "GOOGLE_SHEETS_URL = \"https://docs.google.com/spreadsheets/d/1AAIebjNsnJj_uKALHbXNfn3_YsT6sHXtCU0q7OIPuc4\"\n", - "SHEET_ID = \"0\"\n", - "CSV_SOURCE = f\"{GOOGLE_SHEETS_URL}/export?format=csv&gid={SHEET_ID}\"" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c52f0700", - "metadata": {}, - "outputs": [], - "source": [ - "df = pd.read_csv(CSV_SOURCE)\n", - "df.head()" - ] - }, - { - "cell_type": "markdown", - "id": "6e48d6d1-e935-4244-b9ea-500fdb18e0bd", - "metadata": {}, - "source": [ - "View an interactive panel with your data in one line [1]. As with any Weave Table Panel, you can sort or group by any column to explore and build some intuition for the dataset." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c1e62e5a-6a56-4581-a8e6-4f5d3bcc4d0e", - "metadata": {}, - "outputs": [], - "source": [ - "show(df)" - ] - }, - { - "cell_type": "markdown", - "id": "ad96850c-93e7-4b1f-a479-1e4f3655aebc", - "metadata": { - "scrolled": true - }, - "source": [ - "# 2: Plot the data in Weave\n", - "\n", - "You can convert any Weave Table into a **Weave Plot**. Change the panel type in the expression at the top of the panel from `table` to `plot`:\n", - "\n", - "\n", - "\n", - "Weave infers a reasonable view of your data based on the column types:\n", - "* two numerical columns x and y become the scatter plot axes\n", - "* each row is rendered as an (x, y) point on the resulting 2D grid, with a tooltip showing details on hover\n", - "* if available, the first string-type column becomes the label / the color shown in the legend.\n", - "\n", - "Before we dive into the detailed customization of a Weave Plot, how can we build with and iterate on this starter plot?\n", - "\n", - "## Full-screen a plot: Open as a Weave Board\n", - "\n", - "If you want more visual space or screen real estate to explore—zoom into details, zoom out for more context, iterate on multiple panel views in parallel—open any Weave Panel in a new browser tab. Mouse over the right-hand side and select \"Open in a new tab\"\n", - "\n", - "\n", - "## Multiple views in parallel: Refer to source data as you customize the plot\n", - "\n", - "Duplicate a panel so you can keep one copy in the `table` state and convert the second into a `plot`: click on the horizontal three-dot icon in the top right corner and select \"Duplicate\".\n", - "\n", - "\n", - "\n", - "## Resize one panel to adjust layout & iterate incrementally\n", - "\n", - "Combine these UX moves to iterate quickly on a neat layout: duplicate panels, resize one panel from a corner to a smaller portion of the grid to accommodate more panels, and independently modify individual panels until you're happy with the latest version.\n", - "\n", - "\n", - "Try the whole process starting from the single Table panel below:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b85268d4-ef89-4bf5-9aed-2b8650fcd701", - "metadata": {}, - "outputs": [], - "source": [ - "show(df)" - ] - }, - { - "cell_type": "markdown", - "id": "16cc1620-4d10-41a1-9220-55ca238d5015", - "metadata": {}, - "source": [ - "# 3. Explore a plot with zoom and interactive selection\n", - "\n", - "Interactively explore PanelPlot data with zoom and region highlighting (subset selection):\n", - "\n", - "## Zoom level: Click + drag to zoom into a seleted rectangle, double-click to reset\n", - "To zoom into a region of the plot:\n", - "* click on the magnifying glass icon in the bottom right corner\n", - "* click, hold, and drag to select a set of points—a gray rectangle shows the active selected region\n", - "* optionally repeat as you find new/different points of interest\n", - "* double-click anywhere on the plot area to reset to the original zoom level\n", - "\n", - "## Selected data: Use the .selected_data Weave op to show point details\n", - "To see the full row details for selected points:\n", - "* given one plot panel named `panelN`, create another `panelN+1` and enter `panelN.selected_data` as the new panel's expression. `PanelN+1` will now show any points highlighted in `panelN`\n", - "* to select points from `panelN`, first click on the pointer icon in the bottom right corner of `panelN`\n", - "* click, hold, and drag to select a set of points in `panelN`—a gray rectangle shows the active selected region\n", - "* view the full details for those points in panelN+1\n", - "\n", - "\n", - "\n", - "# 4. Customize Weave Plots: Choose columns or compose Weave expressions\n", - "\n", - "Weave plot panels for scatter, line, and bar charts share most of their configuration parameters. To customize a Weave Plot, click on the `Edit` pencil icon in the top right corner to open the settings menu.\n", - "\n", - "## X axis & Y axis\n", - "\n", - "Define the X and Y dimensions of a plot:\n", - "* choose a column from your source data:`row[\"your column name here\"]` (with helpful suggestions in the dropdown showing available column names as you type)\n", - "* further customize the Weave expression using arithmetic (`row[\"x\"] * 2`), combined columns (`row[\"a\"] + row[\"b\"]`), or more advanced Weave ops (`row[\"cost_per_month\"].avg`)\n", - "\n", - "Try modifying the X and Y of the starter plot: for example, is there a correlation between publication impact (citations) and compute costs? Try setting X = `row[\"Training compute cost (2020 USD)\"]` and Y = `row[\"Citations\"]`\n", - " \n", - "## Colors\n", - "\n", - "Point color is set by one of two input methods:\n", - "* default `Enter a Weave Expression` method: takes the [tableau10 color palette](https://vega.github.io/vega/docs/schemes/#tableau10) and cycles its 10 colors over the distinct values of the result of the Weave Expression, e.g the values of a string column. In our sample plot, the `Color` field defaults to `row[\"System\"]`, and the legend in the top right of the plot shows the [tableau10 colors]((https://vega.github.io/vega/docs/schemes/#tableau10)) repeating over the full list of unique \"System\" names in the source data. Try editing this to `row[\"Domain\"]` to see publication trends by field (the Games and Language models seem to have the highest compute costs).\n", - "* `Select via Dropdown` -> `Encode from series` : this option defaults to one blue color for a single series, and otherwise cycles through the same tableau10 color palette for multiple series\n", - "\n", - "## Tooltip\n", - "\n", - "The tooltip field determines the content displayed when the mouse hovers over a data point. This defaults to a subset of columns from the underlying Weave Table and can be configured via Weave Expression to select one or more columns and optionally link them with string literals for readability/formatting. The following expression might be a useful summary for our sample data to show the authors and date for each model in addition to the system name: `row[\"System\"] + \" - \" + row[\"Authors\"] + \"; \" + row[\"Publication date\"]`\n", - "\n", - "## Labels\n", - "\n", - "This menu (at the very bottom of the plot settings) optionally sets the titles of the X axis, Y axis, and Color legend to the provided text. In our sample, we might condense the X axis title to \"Compute cost\", expand the Color legend title as \"ML task type\", etc.\n", - "\n", - "Here is a [sample side-by-side Weave Board](https://weave.wandb.ai/?exp=get%28%0A++++%22wandb-artifact%3A%2F%2F%2Fstacey%2Fpivot%2Fdefault_plot_with_mods%3Alatest%2Fobj%22%29) of the default starter plot and the final state with all the above modifications (note that the tooltip appears to the bottom right of the cursor/corresponds to the point on the upper left corner of the textbox)\n", - "\n", - "\n", - "## Multiple series\n", - "\n", - "Use the \"New Series +\" button to add one or more series of points. You can treat each of the Series 1, 2, 3... in a plot as an independent group/collection of settings with a separate instance of the specifications described above. When a plot contains multiple series, all of the series are overlayed/rendered on the same plot area when visualized and their settings combined in the legend/titles/etc. With our sample dataset, we may want to configure a different series/visual settings for each domain: language models in one series, computer vision in another, etc.\n", - "\n", - "## Advanced configuration\n", - "\n", - "### Switch linear scale to log scale\n", - "\n", - "There are two ways to convert a plot axis to log scale:\n", - "\n", - "1. Use the **Advanced properties editor**: click the pencil \"edit\" icon on a plot to expand the config menu on the right, then select \"Advanced properties\" at the end of the first 'Properties\" section. Set x- and y- axes to be log scale instead of linear in the dropdown menu.\n", - "\n", - "2. Convert the Weave expression to **log base 10 mathematically**: e.g. convert `row[\"Parameters\"]` to `row[\"Parameters\"] ** 0.1`. This will not adust the grid scale markers like the first option, but it will apply to any expression/dimension (not only x- and y- axes) and can be easier to try in the flow of editing charts.\n", - "\n", - "\n", - "### Filter source data to remove outliers\n", - "\n", - "Sometimes a log scale or zooming in is still insufficient. You can remove outliers by filtering the input data based on a range of column values. Use the `.filter` Weave op on the `Input` field to plot only the points that meet the specified condition. For example, compare the default starter plot with dropping any models that have >= 1e12 parameters:\n", - "\n", - "\n", - "Try editing some of these settings from the Table below:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "4b56735b-f35f-4bb7-bcb6-ce315a16ba30", - "metadata": {}, - "outputs": [], - "source": [ - "show(df)" - ] - }, - { - "cell_type": "markdown", - "id": "dd71e9f7-9c37-440b-8acf-7997baa60065", - "metadata": {}, - "source": [ - "# 5. Scatter plot: Customize point shape and size via explicit assignment or enumeration over option list\n", - "\n", - "The `Mark` setting intially determines the plot style: scatter plot, line plot, bar chart, etc. This defaults to \"auto\" and picks the best option based on the incoming data types. If `Mark` is set explicitly to `point`, this reveals controls for the shape and size of the points.\n", - "\n", - "## Point shape\n", - "\n", - "* defaults to \"Enter a Weave Expression\": as with the other config fields, write a Weave Expression that returns a list, where each distinct option in that list will cycle throught the built-in list of shape options\n", - "* \"Select via dropdown\" -> \"circle\", \"square\", \"cross\", \"diamond\", etc: this input method sets a specific literal point shape from the list of available options\n", - "* \"Select via dropdown\" -> \"Encode from series\": cycle the shape options over the multiple series in the plot\n", - "\n", - "In our example scenario, we could look at how compute sponsorship compares for notable papers: set `Shape` to `row[\"Compute Sponsor Categorization\"]` and observe circles for academia and squares for industry.\n", - "\n", - "## Point size (with approximately-bucketed enumeration)\n", - "\n", - "* default 100 / user-specified number: point size for scatter plots defaults to 100 and can be set to any other numerical value [2]\n", - "* Weave expression: as in other settings, the list of distinct values resulting from the user-specified Weave expression will cycle through five perceptually-distinguishable point sizes from smallest to larges\n", - "\n", - "In this sample plot, try log-scaling point size with compute costs: set `Size` to `row[\"Training compute cost (2020 USD)\"] ** 0.1`. \n", - "\n", - "### Combining all of these suggestions\n", - "\n", - "Here's a static view of the original plot with the modifications described—one more chance to try the fully-interactive and customizable version below\n", - "" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "06b8483f-1db0-4c2d-a27d-e0592a6ce160", - "metadata": {}, - "outputs": [], - "source": [ - "show(df)" - ] - }, - { - "cell_type": "markdown", - "id": "cfc8055c-0406-4b4c-a545-11f8c870d2d4", - "metadata": {}, - "source": [ - "# 6. Inspiration for many possible data exploration workflows\n", - "\n", - "Weave is a maximally general toolkit, and the path of any interactive visual exploration will depend on the data, context, interests, goals, etc. We've described the main options and useful features of Weave Plot to both illustrate concrete steps and hopefully spark your own questions and insights. In follow-up tutorials, we will cover settings specific to line plots and bar charts. We'd love to hear if you discover something interesting in this or your own iterations!\n", - "\n", - "# 7. End notes\n", - "\n", - "## [1] Viewing data in Weave\n", - "\n", - "When starting with a Pandas DataFrame, you have two options for getting data into Weave:\n", - "\n", - "### weave.show(my_dataframe)\n", - "\n", - "`weave.show(my_dataframe)` returns an interactive Weave Panel with the Pandas DataFrame as a Table. This is the fastest and simplest way to load an interactive panel with your data.\n", - "\n", - "### weave.save(weave.from_pandas(my_dataframe), name=\"my_dataframe\")\n", - "\n", - "If you'd like to save the DataFrame as a local object, first wrap it a in format Weave can parse using the `weave.from_pandas` op:\n", - "\n", - "```python\n", - "my_data = weave.save(weave.from_pandas(dataframe), name=\"my_dataframe\")\n", - "my_data\n", - "```\n", - "\n", - "## [2] Configuring point size\n", - "\n", - "Point size is currently \"perceptually clamped\" to around five distinguishable sizes: tiny, small, medium, larger, largest. Increasing/decreasing the literal number will not perceptually increase the biggest points or add more perceptible gradations of size. It may make the smallest points effectively invisible." - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/reference/layout/Each.ipynb b/examples/reference/layout/Each.ipynb deleted file mode 100644 index 17248f033d2..00000000000 --- a/examples/reference/layout/Each.ipynb +++ /dev/null @@ -1,70 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "4093cfd3", - "metadata": {}, - "outputs": [], - "source": [ - "from PIL import Image\n", - "import weave\n", - "from weave.legacy.scripts import syndata\n", - "from weave.legacy import panels" - ] - }, - { - "cell_type": "markdown", - "id": "b05e49a1", - "metadata": {}, - "source": [ - "## PanelEach\n", - "\n", - "PanelEach renders each item in a list.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b2cc5af5", - "metadata": {}, - "outputs": [], - "source": [ - "data = weave.save(syndata.random_metrics(100, 100))\n", - "panels.Each(data)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "36755a04", - "metadata": {}, - "outputs": [], - "source": [ - "ims = weave.save([Image.linear_gradient(\"L\").rotate(i * 4) for i in range(100)])\n", - "panels.Each(ims)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/reference/layout/Group & LabeledItem.ipynb b/examples/reference/layout/Group & LabeledItem.ipynb deleted file mode 100644 index 8f59c238d43..00000000000 --- a/examples/reference/layout/Group & LabeledItem.ipynb +++ /dev/null @@ -1,48 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "2e4a39ce", - "metadata": {}, - "outputs": [], - "source": [ - "import weave\n", - "import weave.legacy.panels" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "32953f12", - "metadata": {}, - "outputs": [], - "source": [ - "# Group example\n", - "weave.legacy.panels.Group(\n", - " items={\n", - " '0': weave.legacy.panels.Group(\n", - " preferHorizontal=True,\n", - " items={\n", - " '0': weave.legacy.panels.LabeledItem(item='item1', label='label1'),\n", - " '1': weave.legacy.panels.LabeledItem(item='item2', label='label2'),\n", - " '2': weave.legacy.panels.LabeledItem(item='item3', label='label3'),\n", - " }\n", - " ),\n", - " '1': weave.legacy.panels.LabeledItem(item='item4', label='label4'),\n", - " '2': weave.legacy.panels.LabeledItem(item='item5', label='label5')\n", - " }\n", - ")" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/reference/markdown.ipynb b/examples/reference/markdown.ipynb deleted file mode 100644 index ee612bac66e..00000000000 --- a/examples/reference/markdown.ipynb +++ /dev/null @@ -1,50 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "9357be91", - "metadata": {}, - "outputs": [], - "source": [ - "import weave" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "42e421dd", - "metadata": {}, - "outputs": [], - "source": [ - "weave.legacy.panels.Board(\n", - " vars={},\n", - " panels=[weave.legacy.panels.BoardPanel(\n", - " weave.legacy.panels.PanelString(\"## I am markdown\\n\\nHear me roar\", mode=\"markdown\"),\n", - " id='some_markdown')\n", - " ])" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/reference/panels/plot/synced_axes.ipynb b/examples/reference/panels/plot/synced_axes.ipynb deleted file mode 100644 index 4f39243c180..00000000000 --- a/examples/reference/panels/plot/synced_axes.ipynb +++ /dev/null @@ -1,153 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "f1022622", - "metadata": {}, - "outputs": [], - "source": [ - "import weave\n", - "import weave.legacy.panels" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "af978245", - "metadata": {}, - "outputs": [], - "source": [ - "from weave.legacy.scripts import syndata" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "4a2a4713", - "metadata": {}, - "outputs": [], - "source": [ - "data = weave.save(syndata.random_metrics(100, 100))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "85e9d219", - "metadata": {}, - "outputs": [], - "source": [ - "import weave\n", - "from weave.legacy import ops_arrow\n", - "from weave.legacy.ecosystem import wandb\n", - "import datetime\n", - "import random\n", - "random.seed(1)\n", - "\n", - "min_x = datetime.datetime(2023, 3, 22, 11, 0, 0)\n", - "max_x = min_x + datetime.timedelta(hours=1)\n", - "\n", - "users = ['nick', 'shawn', 'stacey', 'tim', 'danny']\n", - "model_versions = ['v1', 'v2']\n", - "\n", - "num_requests = 100\n", - "\n", - "def random_datetime_in_range() -> datetime.datetime:\n", - " min_timestamp = min_x.timestamp()\n", - " max_timestamp = max_x.timestamp()\n", - " random_timestamp = random.uniform(min_timestamp, max_timestamp)\n", - " return datetime.datetime.fromtimestamp(random_timestamp)\n", - "\n", - "def random_suggestion(n: int) -> str:\n", - " result = ''\n", - " for i in range(n):\n", - " result += random.choice('abcdefghijklmnopqrstuvwxyz')\n", - " return result\n", - "\n", - "\n", - "items = weave.save(\n", - " ops_arrow.to_arrow(sorted([\n", - " {\n", - " \"pred\": random_suggestion(7),\n", - " \"time\": random_datetime_in_range(),\n", - " \"user\": random.choice(users),\n", - " \"version\": random.choice(model_versions),\n", - " \"accepted\": random.choice([1, 1, 1, 0, 0]),\n", - " \"metric\": random.uniform(0, 1)\n", - " } \n", - " for _ in range(num_requests)\n", - " ], key=lambda d: d['time']))\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3d5919ee", - "metadata": {}, - "outputs": [], - "source": [ - "from weave.legacy.panels.panel_plot import Signals, AxisSelections, Plot\n", - "from weave import weave_internal\n", - "\n", - "\n", - "domain_node = weave_internal.const(None)\n", - "#domain_node = weave_internal.save(None)\n", - "\n", - "board = weave.legacy.panels.Board(\n", - " vars={\n", - " 'predictions': items,\n", - " 'domain': domain_node,\n", - " },\n", - " panels=[\n", - " weave.legacy.panels.BoardPanel(\n", - " lambda predictions, domain: Plot(\n", - " predictions,\n", - " x=lambda row: row[\"time\"],\n", - " y=lambda row: row[\"metric\"],\n", - " label=lambda row: row['user'],\n", - " mark=\"point\",\n", - " domain_x=domain,\n", - " ),\n", - " layout=weave.legacy.panels.BoardPanelLayout(x=0, y=0, w=12, h=12)\n", - " ),\n", - " weave.legacy.panels.BoardPanel(\n", - " lambda predictions, domain: Plot(\n", - " predictions,\n", - " x=lambda row: row[\"time\"],\n", - " y=lambda row: row[\"metric\"] * 2,\n", - " label=lambda row: row['version'],\n", - " mark=\"point\",\n", - " domain_x=domain,\n", - " ),\n", - " layout=weave.legacy.panels.BoardPanelLayout(x=0, y=0, w=12, h=12)\n", - " ), \n", - " ]\n", - ")\n", - "board" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/reference/vis/Distribution.ipynb b/examples/reference/vis/Distribution.ipynb deleted file mode 100644 index 726c4a8f99e..00000000000 --- a/examples/reference/vis/Distribution.ipynb +++ /dev/null @@ -1,138 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "726127b0", - "metadata": {}, - "outputs": [], - "source": [ - "import random\n", - "import weave\n", - "# Weave package now defaults to eager mode, but lazy mode required for this example notebook for now.\\n\n", - "weave.use_lazy_execution()\n", - "from weave.legacy.ecosystem import wandb" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a9875d92", - "metadata": {}, - "outputs": [], - "source": [ - "items = weave.save([\n", - " {'name': 'x',\n", - " 'loss1': [random.gauss(5, 2) for i in range(500)],\n", - " 'loss2': [random.gauss(5, 2) for i in range(500)],\n", - " 'str_val': [random.choice(['a', 'b', 'c']) for i in range(500)]},\n", - " {'name': 'y',\n", - " 'loss1': [random.gauss(9, 4) for i in range(500)],\n", - " 'loss2': [random.gauss(-1, 2) for i in range(500)],\n", - " 'str_val': [random.choice(['a', 'b', 'c']) for i in range(500)]},\n", - " ])\n", - "items" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a92bcc0e", - "metadata": {}, - "outputs": [], - "source": [ - "from weave import weave_internal\n", - "# Numeric distribution\n", - "dist = wandb.Distribution(\n", - " items,\n", - " value_fn=lambda x: x['loss1'],\n", - " bin_size=0.1\n", - ")\n", - "dist" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "fb380da9", - "metadata": {}, - "outputs": [], - "source": [ - "# String distribution\n", - "wandb.Distribution(\n", - " items,\n", - " value_fn=lambda x: x['str_val'],\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "6db75ca1", - "metadata": {}, - "outputs": [], - "source": [ - "# Colored numeric distribution\n", - "wandb.Distribution(\n", - " items,\n", - " value_fn=lambda x: x['loss1'],\n", - " label_fn=lambda x: x['name'],\n", - " bin_size=0.2\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "4eee28b9", - "metadata": {}, - "outputs": [], - "source": [ - "# Colored string distribution\n", - "wandb.Distribution(\n", - " items,\n", - " value_fn=lambda x: x['str_val'],\n", - " label_fn=lambda x: x['name'],\n", - " bin_size=0.2\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "30ab60fb", - "metadata": {}, - "outputs": [], - "source": [ - "# Labeled numeric distribution by nested value\n", - "wandb.Distribution(\n", - " items,\n", - " value_fn=lambda x: x['loss1'],\n", - " label_fn=lambda x: x['str_val'],\n", - " bin_size=0.2\n", - ")" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/reference/vis/Scatter.ipynb b/examples/reference/vis/Scatter.ipynb deleted file mode 100644 index 7584ee9b4cd..00000000000 --- a/examples/reference/vis/Scatter.ipynb +++ /dev/null @@ -1,101 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "81e1af41", - "metadata": {}, - "outputs": [], - "source": [ - "import random\n", - "import weave\n", - "import weave.legacy.panels\n", - "from weave.legacy.show import show\n", - "# Weave package now defaults to eager mode, but lazy mode required for this example notebook for now.\n", - "weave.use_lazy_execution()\n", - "from weave.legacy.ecosystem import wandb" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b6687922", - "metadata": {}, - "outputs": [], - "source": [ - "data = weave.save([{\n", - " 'a': random.gauss(5, 2),\n", - " 'b': random.gauss(0, 9),\n", - " 'c': random.gauss(15, 0.9),\n", - " 'd': random.random(),\n", - " 'e': random.choice(['a', 'b'])} for i in range(500)])\n", - "data" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "726b9294", - "metadata": {}, - "outputs": [], - "source": [ - "panel = weave.legacy.panels.Group(\n", - " items={\n", - " 'scatter': wandb.Scatter(data,\n", - " x_fn=lambda item: item['a'],\n", - " y_fn=lambda item: item['b'],\n", - " label_fn=lambda item: item['e']\n", - " ),\n", - " 'detail': lambda scatter: weave.legacy.panels.Group(\n", - " style=\"height: 400px;\",\n", - " preferHorizontal=True,\n", - " items={\n", - " 'a_dist': weave.legacy.panels.LabeledItem(\n", - " label='Distribution for metric a in selection',\n", - " item=wandb.Distribution(scatter.selected(),\n", - " value_fn=lambda item: item['a'],\n", - " label_fn=lambda item: item['e'],\n", - " bin_size=0.1)),\n", - " 'b_dist': weave.legacy.panels.LabeledItem(\n", - " label='Distribution for metric b in selection',\n", - " item=wandb.Distribution(scatter.selected(),\n", - " value_fn=lambda item: item['b'],\n", - " label_fn=lambda item: item['e'],\n", - " bin_size=0.1))\n", - " }),\n", - " 'table': lambda scatter: weave.legacy.panels.LabeledItem(\n", - " label='Selected items',\n", - " item=weave.legacy.panels.Group(\n", - " style=\"height: 400px;\",\n", - " preferHorizontal=True,\n", - " items={\n", - " 'table': scatter.selected()\n", - " }))\n", - " }\n", - ")\n", - "show(panel, height=1000)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/examples/reference/vis/derived_plots_from_tables.ipynb b/examples/reference/vis/derived_plots_from_tables.ipynb deleted file mode 100644 index 200c338d869..00000000000 --- a/examples/reference/vis/derived_plots_from_tables.ipynb +++ /dev/null @@ -1,111 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "14b320aa", - "metadata": {}, - "outputs": [], - "source": [ - "import weave\n", - "from weave.legacy.ecosystem import wandb\n", - "\n", - "# Group tags are broken with caching\n", - "# import os \n", - "# os.environ[\"WEAVE_NO_CACHE\"] = 'true'" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "bfc9678e", - "metadata": {}, - "outputs": [], - "source": [ - "table = weave.legacy.panels.Table(list(range(100)))\n", - "\n", - "# Add Filter\n", - "table.config.tableState.set_filter_fn(lambda i: i < 50)\n", - "\n", - "# Add Grouping\n", - "table.config.tableState.add_column(lambda i: i % 3, 'col_name_a')\n", - "group_col_id = table.config.tableState.order[0]\n", - "# table.config.tableState.enable_groupby(group_col_id)\n", - "\n", - "# Add Selections (Note: Important to add after grouping so `i` is correctly typed)\n", - "table.config.tableState.add_column(lambda i: i ** 2, 'col_name_b')\n", - "# table.config.tableState.add_column(lambda i: i.max(), 'col_name_c')\n", - "\n", - "# Add Sort\n", - "table.config.tableState.enable_sort(group_col_id)\n", - "\n", - "\n", - "panel = weave.legacy.panels.Group(\n", - " layoutMode='horizontal',\n", - " items={\n", - " \"table\": table,\n", - "# \"table_2\": lambda table: table.rows()[0]['col_name_a']\n", - " \"plot\": lambda table: weave.legacy.panels.Plot(\n", - " table.all_rows(),\n", - " x=lambda row: row['col_name_a'],\n", - " y=lambda row: row['col_name_b'],\n", - " )\n", - " }\n", - ")\n", - "panel" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "bf475571", - "metadata": {}, - "outputs": [], - "source": [ - "panel = weave.legacy.panels.Group(\n", - " layoutMode='horizontal',\n", - " items={\n", - " \"table\": weave.legacy.panels.Table(list(range(100)), columns=[\n", - " lambda i: i,\n", - " lambda i: i**2,\n", - " lambda i: i * 0 + 1\n", - " ]),\n", - " \"table_2\": lambda table: weave.legacy.panels.Table(table.all_rows(), columns=[\n", - " lambda i: table.all_rows()['c_0'].max() - i['c_0'],\n", - " lambda i: i['c_1'] / 2,\n", - " lambda i: i['c_2'] - 2\n", - " ]),\n", - " \"plot\": lambda table, table_2: weave.legacy.panels.Plot(\n", - " weave.legacy.ops.make_list(a=table.all_rows(), b=table_2.all_rows()).concat(),\n", - " x=lambda row: row['c_0'],\n", - " y=lambda row: row['c_1'],\n", - " color=lambda row: row['c_2']\n", - " )\n", - " }\n", - ")\n", - "panel" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/integration_test/cypress/e2e/interactive/blank.py b/integration_test/cypress/e2e/interactive/blank.py index ef823b13559..0dc51529825 100644 --- a/integration_test/cypress/e2e/interactive/blank.py +++ b/integration_test/cypress/e2e/interactive/blank.py @@ -1,12 +1,12 @@ import weave -from weave.legacy.show import show_url +from weave.legacy.weave.show import show_url weave.use_fixed_server_port() obj = [ 1, 2, 3, -] # weave.legacy.panels.Board({}, [weave.legacy.panels.BoardPanel(weave.legacy.panels.Table([1, 2, 3]))]) +] # weave.legacy.weave.panels.Board({}, [weave.legacy.weave.panels.BoardPanel(weave.legacy.weave.panels.Table([1, 2, 3]))]) blank = weave.save(obj) print(show_url(obj)) diff --git a/integration_test/cypress/e2e/interactive/distribution.py b/integration_test/cypress/e2e/interactive/distribution.py index e81e06a7c78..f516a04364c 100644 --- a/integration_test/cypress/e2e/interactive/distribution.py +++ b/integration_test/cypress/e2e/interactive/distribution.py @@ -1,12 +1,12 @@ import random import weave -from weave.legacy import panels -from weave.legacy.show import show_url +from weave.legacy.weave import panels +from weave.legacy.weave.show import show_url # Weave package now defaults to eager mode, but lazy mode required for this example notebook for now. weave.use_lazy_execution() -from weave.legacy.ecosystem import wandb +from weave.legacy.weave.ecosystem import wandb weave.use_fixed_server_port() @@ -38,7 +38,8 @@ {}, [ panels.BoardPanel( - panel, layout=weave.legacy.panels.BoardPanelLayout(x=0, y=0, w=24, h=12) + panel, + layout=weave.legacy.weave.panels.BoardPanelLayout(x=0, y=0, w=24, h=12), ) ], ) diff --git a/integration_test/cypress/e2e/interactive/scatter.py b/integration_test/cypress/e2e/interactive/scatter.py index 0c0b117866d..b0e170fcc56 100644 --- a/integration_test/cypress/e2e/interactive/scatter.py +++ b/integration_test/cypress/e2e/interactive/scatter.py @@ -1,8 +1,8 @@ import random import weave -from weave.legacy.ecosystem import wandb -from weave.legacy.show import show_url +from weave.legacy.weave.ecosystem import wandb +from weave.legacy.weave.show import show_url weave.use_fixed_server_port() @@ -19,14 +19,14 @@ ] ) -panel: weave.legacy.panels.Group = weave.legacy.panels.Group( +panel: weave.legacy.weave.panels.Group = weave.legacy.weave.panels.Group( items={ "scatter": wandb.Scatter( # type: ignore data, x_fn=lambda item: item["a"], y_fn=lambda item: item["b"] ), - "table": lambda scatter: weave.legacy.panels.LabeledItem( + "table": lambda scatter: weave.legacy.weave.panels.LabeledItem( label="Selected items", - item=weave.legacy.panels.Group( + item=weave.legacy.weave.panels.Group( style="height: 400px;", preferHorizontal=True, items={"table": scatter.selected()}, diff --git a/integration_test/cypress/e2e/notebooks/experimental/AutoBoard.cy.ts b/integration_test/cypress/e2e/notebooks/experimental/AutoBoard.cy.ts index 846e7a7aaf5..dff3492376a 100644 --- a/integration_test/cypress/e2e/notebooks/experimental/AutoBoard.cy.ts +++ b/integration_test/cypress/e2e/notebooks/experimental/AutoBoard.cy.ts @@ -1,7 +1,8 @@ import {checkWeaveNotebookOutputs} from '../notebooks'; -describe('../examples/experimental/AutoBoard.ipynb notebook test', () => { - it('passes', () => - checkWeaveNotebookOutputs('../examples/experimental/AutoBoard.ipynb') - ); -}); \ No newline at end of file +describe('../weave/legacy/examples/experimental/AutoBoard.ipynb notebook test', () => { + it('passes', () => + checkWeaveNotebookOutputs( + '../weave/legacy/examples/experimental/AutoBoard.ipynb' + )); +}); diff --git a/integration_test/cypress/e2e/notebooks/experimental/Monitor.cy.ts b/integration_test/cypress/e2e/notebooks/experimental/Monitor.cy.ts index c7386eea2ec..2a54869af2f 100644 --- a/integration_test/cypress/e2e/notebooks/experimental/Monitor.cy.ts +++ b/integration_test/cypress/e2e/notebooks/experimental/Monitor.cy.ts @@ -1,7 +1,8 @@ import {checkWeaveNotebookOutputs} from '../notebooks'; -describe('../examples/experimental/Monitor.ipynb notebook test', () => { - it('passes', () => - checkWeaveNotebookOutputs('../examples/experimental/Monitor.ipynb') - ); -}); \ No newline at end of file +describe('../weave/legacy/examples/experimental/Monitor.ipynb notebook test', () => { + it('passes', () => + checkWeaveNotebookOutputs( + '../weave/legacy/examples/experimental/Monitor.ipynb' + )); +}); diff --git a/integration_test/cypress/e2e/notebooks/experimental/MonitorPanelPlot.cy.ts b/integration_test/cypress/e2e/notebooks/experimental/MonitorPanelPlot.cy.ts index 149bae1d569..e8f2bfa5958 100644 --- a/integration_test/cypress/e2e/notebooks/experimental/MonitorPanelPlot.cy.ts +++ b/integration_test/cypress/e2e/notebooks/experimental/MonitorPanelPlot.cy.ts @@ -1,7 +1,8 @@ import {checkWeaveNotebookOutputs} from '../notebooks'; -describe('../examples/experimental/MonitorPanelPlot.ipynb notebook test', () => { - it('passes', () => - checkWeaveNotebookOutputs('../examples/experimental/MonitorPanelPlot.ipynb') - ); -}); \ No newline at end of file +describe('../weave/legacy/examples/experimental/MonitorPanelPlot.ipynb notebook test', () => { + it('passes', () => + checkWeaveNotebookOutputs( + '../weave/legacy/examples/experimental/MonitorPanelPlot.ipynb' + )); +}); diff --git a/integration_test/cypress/e2e/notebooks/experimental/Mutations.cy.ts b/integration_test/cypress/e2e/notebooks/experimental/Mutations.cy.ts index 7caa04330d0..7da28147003 100644 --- a/integration_test/cypress/e2e/notebooks/experimental/Mutations.cy.ts +++ b/integration_test/cypress/e2e/notebooks/experimental/Mutations.cy.ts @@ -1,7 +1,8 @@ import {checkWeaveNotebookOutputs} from '../notebooks'; -describe('../examples/experimental/Mutations.ipynb notebook test', () => { - it('passes', () => - checkWeaveNotebookOutputs('../examples/experimental/Mutations.ipynb') - ); -}); \ No newline at end of file +describe('../weave/legacy/examples/experimental/Mutations.ipynb notebook test', () => { + it('passes', () => + checkWeaveNotebookOutputs( + '../weave/legacy/examples/experimental/Mutations.ipynb' + )); +}); diff --git a/integration_test/cypress/e2e/notebooks/experimental/app/RunChain.cy.ts b/integration_test/cypress/e2e/notebooks/experimental/app/RunChain.cy.ts index 38be88c7892..418dc2ff776 100644 --- a/integration_test/cypress/e2e/notebooks/experimental/app/RunChain.cy.ts +++ b/integration_test/cypress/e2e/notebooks/experimental/app/RunChain.cy.ts @@ -1,10 +1,10 @@ import {checkWeaveNotebookOutputs} from '../../notebooks'; -describe('../examples/experimental/app/RunChain.ipynb notebook test', () => { +describe('../weave/legacy/examples/experimental/app/RunChain.ipynb notebook test', () => { // Skipping until this is stable it.skip('passes', () => { return checkWeaveNotebookOutputs( - '../examples/experimental/app/RunChain.ipynb' + '../weave/legacy/examples/experimental/app/RunChain.ipynb' ); }); }); diff --git a/integration_test/cypress/e2e/notebooks/experimental/app/beautiful_runs.cy.ts b/integration_test/cypress/e2e/notebooks/experimental/app/beautiful_runs.cy.ts index 3a817c65bdf..5b25ef8202e 100644 --- a/integration_test/cypress/e2e/notebooks/experimental/app/beautiful_runs.cy.ts +++ b/integration_test/cypress/e2e/notebooks/experimental/app/beautiful_runs.cy.ts @@ -1,7 +1,8 @@ import {checkWeaveNotebookOutputs} from '../../notebooks'; -describe('../examples/experimental/app/beautiful_runs.ipynb notebook test', () => { - it('passes', () => - checkWeaveNotebookOutputs('../examples/experimental/app/beautiful_runs.ipynb') - ); -}); \ No newline at end of file +describe('../weave/legacy/examples/experimental/app/beautiful_runs.ipynb notebook test', () => { + it('passes', () => + checkWeaveNotebookOutputs( + '../weave/legacy/examples/experimental/app/beautiful_runs.ipynb' + )); +}); diff --git a/integration_test/cypress/e2e/notebooks/experimental/app/scenario_compare.cy.ts b/integration_test/cypress/e2e/notebooks/experimental/app/scenario_compare.cy.ts index d15058f6d8e..ec4ab75457b 100644 --- a/integration_test/cypress/e2e/notebooks/experimental/app/scenario_compare.cy.ts +++ b/integration_test/cypress/e2e/notebooks/experimental/app/scenario_compare.cy.ts @@ -1,7 +1,8 @@ import {checkWeaveNotebookOutputs} from '../../notebooks'; -describe('../examples/experimental/app/scenario_compare.ipynb notebook test', () => { - it('passes', () => - checkWeaveNotebookOutputs('../examples/experimental/app/scenario_compare.ipynb') - ); -}); \ No newline at end of file +describe('../weave/legacy/examples/experimental/app/scenario_compare.ipynb notebook test', () => { + it('passes', () => + checkWeaveNotebookOutputs( + '../weave/legacy/examples/experimental/app/scenario_compare.ipynb' + )); +}); diff --git a/integration_test/cypress/e2e/notebooks/experimental/dir_browsing.cy.ts b/integration_test/cypress/e2e/notebooks/experimental/dir_browsing.cy.ts index 27eb8ac7c90..33b8eb15f85 100644 --- a/integration_test/cypress/e2e/notebooks/experimental/dir_browsing.cy.ts +++ b/integration_test/cypress/e2e/notebooks/experimental/dir_browsing.cy.ts @@ -1,7 +1,8 @@ import {checkWeaveNotebookOutputs} from '../notebooks'; -describe('../examples/experimental/dir_browsing.ipynb notebook test', () => { - it('passes', () => - checkWeaveNotebookOutputs('../examples/experimental/dir_browsing.ipynb') - ); -}); \ No newline at end of file +describe('../weave/legacy/examples/experimental/dir_browsing.ipynb notebook test', () => { + it('passes', () => + checkWeaveNotebookOutputs( + '../weave/legacy/examples/experimental/dir_browsing.ipynb' + )); +}); diff --git a/integration_test/cypress/e2e/notebooks/experimental/ecosystem.cy.ts b/integration_test/cypress/e2e/notebooks/experimental/ecosystem.cy.ts index 51f591f77b2..45b124b806d 100644 --- a/integration_test/cypress/e2e/notebooks/experimental/ecosystem.cy.ts +++ b/integration_test/cypress/e2e/notebooks/experimental/ecosystem.cy.ts @@ -1,7 +1,8 @@ import {checkWeaveNotebookOutputs} from '../notebooks'; -describe('../examples/experimental/ecosystem.ipynb notebook test', () => { - it('passes', () => - checkWeaveNotebookOutputs('../examples/experimental/ecosystem.ipynb') - ); -}); \ No newline at end of file +describe('../weave/legacy/examples/experimental/ecosystem.ipynb notebook test', () => { + it('passes', () => + checkWeaveNotebookOutputs( + '../weave/legacy/examples/experimental/ecosystem.ipynb' + )); +}); diff --git a/integration_test/cypress/e2e/notebooks/experimental/image_gen_craiyon.cy.ts b/integration_test/cypress/e2e/notebooks/experimental/image_gen_craiyon.cy.ts index d9427192dd8..ce8e9acb66f 100644 --- a/integration_test/cypress/e2e/notebooks/experimental/image_gen_craiyon.cy.ts +++ b/integration_test/cypress/e2e/notebooks/experimental/image_gen_craiyon.cy.ts @@ -1,7 +1,8 @@ import {checkWeaveNotebookOutputs} from '../notebooks'; -describe('../examples/experimental/image_gen_craiyon.ipynb notebook test', () => { - it('passes', () => - checkWeaveNotebookOutputs('../examples/experimental/image_gen_craiyon.ipynb') - ); -}); \ No newline at end of file +describe('../weave/legacy/examples/experimental/image_gen_craiyon.ipynb notebook test', () => { + it('passes', () => + checkWeaveNotebookOutputs( + '../weave/legacy/examples/experimental/image_gen_craiyon.ipynb' + )); +}); diff --git a/integration_test/cypress/e2e/notebooks/experimental/layout_panels.cy.ts b/integration_test/cypress/e2e/notebooks/experimental/layout_panels.cy.ts index 5b41c7b27ab..536aa496cd4 100644 --- a/integration_test/cypress/e2e/notebooks/experimental/layout_panels.cy.ts +++ b/integration_test/cypress/e2e/notebooks/experimental/layout_panels.cy.ts @@ -1,7 +1,8 @@ import {checkWeaveNotebookOutputs} from '../notebooks'; -describe('../examples/experimental/layout_panels.ipynb notebook test', () => { - it('passes', () => - checkWeaveNotebookOutputs('../examples/experimental/layout_panels.ipynb') - ); -}); \ No newline at end of file +describe('../weave/legacy/examples/experimental/layout_panels.ipynb notebook test', () => { + it('passes', () => + checkWeaveNotebookOutputs( + '../weave/legacy/examples/experimental/layout_panels.ipynb' + )); +}); diff --git a/integration_test/cypress/e2e/notebooks/experimental/spacy_demo.cy.ts b/integration_test/cypress/e2e/notebooks/experimental/spacy_demo.cy.ts index e1808d5b81a..81563c82ba1 100644 --- a/integration_test/cypress/e2e/notebooks/experimental/spacy_demo.cy.ts +++ b/integration_test/cypress/e2e/notebooks/experimental/spacy_demo.cy.ts @@ -1,7 +1,8 @@ import {checkWeaveNotebookOutputs} from '../notebooks'; -describe('../examples/experimental/spacy_demo.ipynb notebook test', () => { - it('passes', () => - checkWeaveNotebookOutputs('../examples/experimental/spacy_demo.ipynb') - ); -}); \ No newline at end of file +describe('../weave/legacy/examples/experimental/spacy_demo.ipynb notebook test', () => { + it('passes', () => + checkWeaveNotebookOutputs( + '../weave/legacy/examples/experimental/spacy_demo.ipynb' + )); +}); diff --git a/integration_test/cypress/e2e/notebooks/experimental/table_data_passing.cy.ts b/integration_test/cypress/e2e/notebooks/experimental/table_data_passing.cy.ts index 1cec68fa389..dda193c3bda 100644 --- a/integration_test/cypress/e2e/notebooks/experimental/table_data_passing.cy.ts +++ b/integration_test/cypress/e2e/notebooks/experimental/table_data_passing.cy.ts @@ -1,7 +1,8 @@ import {checkWeaveNotebookOutputs} from '../notebooks'; -describe('../examples/experimental/table_data_passing.ipynb notebook test', () => { - it('passes', () => - checkWeaveNotebookOutputs('../examples/experimental/table_data_passing.ipynb') - ); -}); \ No newline at end of file +describe('../weave/legacy/examples/experimental/table_data_passing.ipynb notebook test', () => { + it('passes', () => + checkWeaveNotebookOutputs( + '../weave/legacy/examples/experimental/table_data_passing.ipynb' + )); +}); diff --git a/integration_test/cypress/e2e/notebooks/experimental/tag_search.cy.ts b/integration_test/cypress/e2e/notebooks/experimental/tag_search.cy.ts index 8a0a590a263..ed077f50955 100644 --- a/integration_test/cypress/e2e/notebooks/experimental/tag_search.cy.ts +++ b/integration_test/cypress/e2e/notebooks/experimental/tag_search.cy.ts @@ -1,7 +1,8 @@ import {checkWeaveNotebookOutputs} from '../notebooks'; -describe('../examples/experimental/tag_search.ipynb notebook test', () => { - it('passes', () => - checkWeaveNotebookOutputs('../examples/experimental/tag_search.ipynb') - ); -}); \ No newline at end of file +describe('../weave/legacy/examples/experimental/tag_search.ipynb notebook test', () => { + it('passes', () => + checkWeaveNotebookOutputs( + '../weave/legacy/examples/experimental/tag_search.ipynb' + )); +}); diff --git a/integration_test/cypress/e2e/notebooks/get_started.cy.ts b/integration_test/cypress/e2e/notebooks/get_started.cy.ts index d5a5864ed38..edc71adaa1a 100644 --- a/integration_test/cypress/e2e/notebooks/get_started.cy.ts +++ b/integration_test/cypress/e2e/notebooks/get_started.cy.ts @@ -1,7 +1,7 @@ -import {checkWeaveNotebookOutputs} from './notebooks'; +import { checkWeaveNotebookOutputs } from './notebooks'; -describe('../examples/get_started.ipynb notebook test', () => { +describe('../weave/legacy/examples/get_started.ipynb notebook test', () => { it('passes', () => - checkWeaveNotebookOutputs('../examples/get_started.ipynb') + checkWeaveNotebookOutputs('../weave/legacy/examples/get_started.ipynb') ); -}); \ No newline at end of file +}); diff --git a/integration_test/cypress/e2e/notebooks/reference/WB_API.cy.ts b/integration_test/cypress/e2e/notebooks/reference/WB_API.cy.ts index ee5392842b8..95b70e8b197 100644 --- a/integration_test/cypress/e2e/notebooks/reference/WB_API.cy.ts +++ b/integration_test/cypress/e2e/notebooks/reference/WB_API.cy.ts @@ -1,7 +1,8 @@ import {checkWeaveNotebookOutputs} from '../notebooks'; -describe('../examples/reference/WB_API.ipynb notebook test', () => { - it('passes', () => - checkWeaveNotebookOutputs('../examples/reference/WB_API.ipynb') - ); -}); \ No newline at end of file +describe('../weave/legacy/examples/reference/WB_API.ipynb notebook test', () => { + it('passes', () => + checkWeaveNotebookOutputs( + '../weave/legacy/examples/reference/WB_API.ipynb' + )); +}); diff --git a/integration_test/cypress/e2e/notebooks/reference/basic_weave_types.cy.ts b/integration_test/cypress/e2e/notebooks/reference/basic_weave_types.cy.ts index ffc884c3b6a..1d5b0f2db26 100644 --- a/integration_test/cypress/e2e/notebooks/reference/basic_weave_types.cy.ts +++ b/integration_test/cypress/e2e/notebooks/reference/basic_weave_types.cy.ts @@ -1,7 +1,8 @@ import {checkWeaveNotebookOutputs} from '../notebooks'; -describe('../examples/reference/basic_weave_types.ipynb notebook test', () => { - it('passes', () => - checkWeaveNotebookOutputs('../examples/reference/basic_weave_types.ipynb') - ); -}); \ No newline at end of file +describe('../weave/legacy/examples/reference/basic_weave_types.ipynb notebook test', () => { + it('passes', () => + checkWeaveNotebookOutputs( + '../weave/legacy/examples/reference/basic_weave_types.ipynb' + )); +}); diff --git a/integration_test/cypress/e2e/notebooks/reference/confusion_matrix.cy.ts b/integration_test/cypress/e2e/notebooks/reference/confusion_matrix.cy.ts index 76b05398ce1..3be38b4eb2e 100644 --- a/integration_test/cypress/e2e/notebooks/reference/confusion_matrix.cy.ts +++ b/integration_test/cypress/e2e/notebooks/reference/confusion_matrix.cy.ts @@ -1,7 +1,8 @@ import {checkWeaveNotebookOutputs} from '../notebooks'; -describe('../examples/reference/confusion_matrix.ipynb notebook test', () => { - it('passes', () => - checkWeaveNotebookOutputs('../examples/reference/confusion_matrix.ipynb') - ); -}); \ No newline at end of file +describe('../weave/legacy/examples/reference/confusion_matrix.ipynb notebook test', () => { + it('passes', () => + checkWeaveNotebookOutputs( + '../weave/legacy/examples/reference/confusion_matrix.ipynb' + )); +}); diff --git a/integration_test/cypress/e2e/notebooks/reference/control/Object Picker.cy.ts b/integration_test/cypress/e2e/notebooks/reference/control/Object Picker.cy.ts index 125ff013a01..58a8525d577 100644 --- a/integration_test/cypress/e2e/notebooks/reference/control/Object Picker.cy.ts +++ b/integration_test/cypress/e2e/notebooks/reference/control/Object Picker.cy.ts @@ -1,7 +1,8 @@ import {checkWeaveNotebookOutputs} from '../../notebooks'; -describe('../examples/reference/control/Object Picker.ipynb notebook test', () => { - it('passes', () => - checkWeaveNotebookOutputs('../examples/reference/control/Object Picker.ipynb') - ); -}); \ No newline at end of file +describe('../weave/legacy/examples/reference/control/Object Picker.ipynb notebook test', () => { + it('passes', () => + checkWeaveNotebookOutputs( + '../weave/legacy/examples/reference/control/Object Picker.ipynb' + )); +}); diff --git a/integration_test/cypress/e2e/notebooks/reference/control/Slider.cy.ts b/integration_test/cypress/e2e/notebooks/reference/control/Slider.cy.ts index 742dd891f77..b1413fd62b1 100644 --- a/integration_test/cypress/e2e/notebooks/reference/control/Slider.cy.ts +++ b/integration_test/cypress/e2e/notebooks/reference/control/Slider.cy.ts @@ -1,7 +1,8 @@ import {checkWeaveNotebookOutputs} from '../../notebooks'; -describe('../examples/reference/control/Slider.ipynb notebook test', () => { - it('passes', () => - checkWeaveNotebookOutputs('../examples/reference/control/Slider.ipynb') - ); -}); \ No newline at end of file +describe('../weave/legacy/examples/reference/control/Slider.ipynb notebook test', () => { + it('passes', () => + checkWeaveNotebookOutputs( + '../weave/legacy/examples/reference/control/Slider.ipynb' + )); +}); diff --git a/integration_test/cypress/e2e/notebooks/reference/create_plots_ui_guide.cy.ts b/integration_test/cypress/e2e/notebooks/reference/create_plots_ui_guide.cy.ts index 7b8e106b9ea..dac28852865 100644 --- a/integration_test/cypress/e2e/notebooks/reference/create_plots_ui_guide.cy.ts +++ b/integration_test/cypress/e2e/notebooks/reference/create_plots_ui_guide.cy.ts @@ -1,7 +1,8 @@ import {checkWeaveNotebookOutputs} from '../notebooks'; -describe('../examples/reference/create_plots_ui_guide.ipynb notebook test', () => { - it('passes', () => - checkWeaveNotebookOutputs('../examples/reference/create_plots_ui_guide.ipynb') - ); -}); \ No newline at end of file +describe('../weave/legacy/examples/reference/create_plots_ui_guide.ipynb notebook test', () => { + it('passes', () => + checkWeaveNotebookOutputs( + '../weave/legacy/examples/reference/create_plots_ui_guide.ipynb' + )); +}); diff --git a/integration_test/cypress/e2e/notebooks/reference/layout/Each.cy.ts b/integration_test/cypress/e2e/notebooks/reference/layout/Each.cy.ts index bedfb78564e..2979cd6496b 100644 --- a/integration_test/cypress/e2e/notebooks/reference/layout/Each.cy.ts +++ b/integration_test/cypress/e2e/notebooks/reference/layout/Each.cy.ts @@ -1,7 +1,8 @@ import {checkWeaveNotebookOutputs} from '../../notebooks'; -describe('../examples/reference/layout/Each.ipynb notebook test', () => { - it('passes', () => - checkWeaveNotebookOutputs('../examples/reference/layout/Each.ipynb') - ); -}); \ No newline at end of file +describe('../weave/legacy/examples/reference/layout/Each.ipynb notebook test', () => { + it('passes', () => + checkWeaveNotebookOutputs( + '../weave/legacy/examples/reference/layout/Each.ipynb' + )); +}); diff --git a/integration_test/cypress/e2e/notebooks/reference/layout/Group & LabeledItem.cy.ts b/integration_test/cypress/e2e/notebooks/reference/layout/Group & LabeledItem.cy.ts index 418b2e46deb..1dc98c1a348 100644 --- a/integration_test/cypress/e2e/notebooks/reference/layout/Group & LabeledItem.cy.ts +++ b/integration_test/cypress/e2e/notebooks/reference/layout/Group & LabeledItem.cy.ts @@ -1,7 +1,8 @@ import {checkWeaveNotebookOutputs} from '../../notebooks'; -describe('../examples/reference/layout/Group & LabeledItem.ipynb notebook test', () => { - it('passes', () => - checkWeaveNotebookOutputs('../examples/reference/layout/Group & LabeledItem.ipynb') - ); -}); \ No newline at end of file +describe('../weave/legacy/examples/reference/layout/Group & LabeledItem.ipynb notebook test', () => { + it('passes', () => + checkWeaveNotebookOutputs( + '../weave/legacy/examples/reference/layout/Group & LabeledItem.ipynb' + )); +}); diff --git a/integration_test/cypress/e2e/notebooks/reference/panels/plot/synced_axes.cy.ts b/integration_test/cypress/e2e/notebooks/reference/panels/plot/synced_axes.cy.ts index 07cdc0e2c74..bdf79decb3f 100644 --- a/integration_test/cypress/e2e/notebooks/reference/panels/plot/synced_axes.cy.ts +++ b/integration_test/cypress/e2e/notebooks/reference/panels/plot/synced_axes.cy.ts @@ -1,7 +1,8 @@ import {checkWeaveNotebookOutputs} from '../../../notebooks'; -describe('../examples/reference/panels/plot/synced_axes.ipynb notebook test', () => { - it('passes', () => - checkWeaveNotebookOutputs('../examples/reference/panels/plot/synced_axes.ipynb') - ); -}); \ No newline at end of file +describe('../weave/legacy/examples/reference/panels/plot/synced_axes.ipynb notebook test', () => { + it('passes', () => + checkWeaveNotebookOutputs( + '../weave/legacy/examples/reference/panels/plot/synced_axes.ipynb' + )); +}); diff --git a/integration_test/cypress/e2e/notebooks/reference/vis/Distribution.cy.ts b/integration_test/cypress/e2e/notebooks/reference/vis/Distribution.cy.ts index d4c1a853819..5acc7678425 100644 --- a/integration_test/cypress/e2e/notebooks/reference/vis/Distribution.cy.ts +++ b/integration_test/cypress/e2e/notebooks/reference/vis/Distribution.cy.ts @@ -1,7 +1,8 @@ import {checkWeaveNotebookOutputs} from '../../notebooks'; -describe('../examples/reference/vis/Distribution.ipynb notebook test', () => { - it('passes', () => - checkWeaveNotebookOutputs('../examples/reference/vis/Distribution.ipynb') - ); -}); \ No newline at end of file +describe('../weave/legacy/examples/reference/vis/Distribution.ipynb notebook test', () => { + it('passes', () => + checkWeaveNotebookOutputs( + '../weave/legacy/examples/reference/vis/Distribution.ipynb' + )); +}); diff --git a/integration_test/cypress/e2e/notebooks/reference/vis/Scatter.cy.ts b/integration_test/cypress/e2e/notebooks/reference/vis/Scatter.cy.ts index f50850fd37b..9b39b237944 100644 --- a/integration_test/cypress/e2e/notebooks/reference/vis/Scatter.cy.ts +++ b/integration_test/cypress/e2e/notebooks/reference/vis/Scatter.cy.ts @@ -1,7 +1,8 @@ import {checkWeaveNotebookOutputs} from '../../notebooks'; -describe('../examples/reference/vis/Scatter.ipynb notebook test', () => { - it('passes', () => - checkWeaveNotebookOutputs('../examples/reference/vis/Scatter.ipynb') - ); -}); \ No newline at end of file +describe('../weave/legacy/examples/reference/vis/Scatter.ipynb notebook test', () => { + it('passes', () => + checkWeaveNotebookOutputs( + '../weave/legacy/examples/reference/vis/Scatter.ipynb' + )); +}); diff --git a/integration_test/cypress/e2e/notebooks/reference/vis/derived_plots_from_tables.cy.ts b/integration_test/cypress/e2e/notebooks/reference/vis/derived_plots_from_tables.cy.ts index f701fc88836..072da256384 100644 --- a/integration_test/cypress/e2e/notebooks/reference/vis/derived_plots_from_tables.cy.ts +++ b/integration_test/cypress/e2e/notebooks/reference/vis/derived_plots_from_tables.cy.ts @@ -1,7 +1,8 @@ import {checkWeaveNotebookOutputs} from '../../notebooks'; -describe('../examples/reference/vis/derived_plots_from_tables.ipynb notebook test', () => { - it('passes', () => - checkWeaveNotebookOutputs('../examples/reference/vis/derived_plots_from_tables.ipynb') - ); -}); \ No newline at end of file +describe('../weave/legacy/examples/reference/vis/derived_plots_from_tables.ipynb notebook test', () => { + it('passes', () => + checkWeaveNotebookOutputs( + '../weave/legacy/examples/reference/vis/derived_plots_from_tables.ipynb' + )); +}); diff --git a/my_patch.patch b/my_patch.patch deleted file mode 100644 index 04b01bb37f6..00000000000 --- a/my_patch.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/weave/__init__.py b/weave/__init__.py -index b77fa857..a45a1532 100644 ---- a/weave/__init__.py -+++ b/weave/__init__.py -@@ -12,8 +12,6 @@ from .graph import Node # used as a type in op definitions - from .show import show, show_url - from .api import * - --use_frontend_devmode() -- - from . import panels - from .panel import Panel - diff --git a/mypy.ini b/mypy.ini index b8c601a669f..8793034417e 100644 --- a/mypy.ini +++ b/mypy.ini @@ -7,19 +7,19 @@ exclude = (.*pyi$)|(weave/ecosystem)|(weave/tests)|(weave/panel)|(weave/ops)|(we ;; 1. put in exclude above ;; 2. put in follow_imports = skip below ;; 3. put in exclude in .pre-commit-config.yaml -[mypy-weave.legacy.ecosystem.*] +[mypy-weave.legacy.weave.ecosystem.*] follow_imports = skip -[mypy-weave.legacy.panels_py.*] +[mypy-weave.legacy.weave.panels_py.*] follow_imports = skip -[mypy-weave.legacy.panels.*] +[mypy-weave.legacy.weave.panels.*] follow_imports = skip -[mypy-weave.legacy.ops_primitives.*] +[mypy-weave.legacy.weave.ops_primitives.*] follow_imports = skip -[mypy-weave.legacy.ops_domain.*] +[mypy-weave.legacy.weave.ops_domain.*] follow_imports = skip [mypy-pyarrow.*] @@ -95,15 +95,15 @@ disallow_untyped_calls = True ; show_error_codes = True -[mypy-weave.legacy._dict_utils] +[mypy-weave.legacy.weave._dict_utils] disallow_untyped_defs = False disallow_untyped_calls = False -[mypy-weave.legacy.arrow.*] +[mypy-weave.legacy.weave.arrow.*] disallow_untyped_defs = False disallow_untyped_calls = False -[mypy-weave.legacy.ops_arrow.*] +[mypy-weave.legacy.weave.ops_arrow.*] disallow_untyped_defs = False disallow_untyped_calls = False @@ -131,19 +131,19 @@ disallow_untyped_calls = False disallow_untyped_defs = False disallow_untyped_calls = False -[mypy-weave.legacy.arrow_util] +[mypy-weave.legacy.weave.arrow_util] disallow_untyped_defs = False disallow_untyped_calls = False -[mypy-weave.legacy.box] +[mypy-weave.legacy.weave.box] disallow_untyped_defs = False disallow_untyped_calls = False -[mypy-weave.legacy.mappers] +[mypy-weave.legacy.weave.mappers] disallow_untyped_defs = False disallow_untyped_calls = False -[mypy-weave.legacy.client_interface] +[mypy-weave.legacy.weave.client_interface] disallow_untyped_defs = False disallow_untyped_calls = False @@ -151,11 +151,11 @@ disallow_untyped_calls = False disallow_untyped_defs = False disallow_untyped_calls = False -[mypy-weave.legacy.weave_types] +[mypy-weave.legacy.weave.weave_types] disallow_untyped_defs = False disallow_untyped_calls = False -[mypy-weave.legacy.uris] +[mypy-weave.legacy.weave.uris] disallow_untyped_defs = False disallow_untyped_calls = False @@ -163,15 +163,15 @@ disallow_untyped_calls = False disallow_untyped_defs = False disallow_untyped_calls = False -[mypy-weave.legacy.op_args] +[mypy-weave.legacy.weave.op_args] disallow_untyped_defs = False disallow_untyped_calls = False -[mypy-weave.legacy.context_state] +[mypy-weave.legacy.weave.context_state] disallow_untyped_defs = False disallow_untyped_calls = False -[mypy-weave.legacy.mappers_weave] +[mypy-weave.legacy.weave.mappers_weave] disallow_untyped_defs = False disallow_untyped_calls = False @@ -183,15 +183,15 @@ disallow_untyped_calls = False disallow_untyped_defs = False disallow_untyped_calls = False -[mypy-weave.legacy.artifact_base] +[mypy-weave.legacy.weave.artifact_base] disallow_untyped_defs = False disallow_untyped_calls = False -[mypy-weave.legacy.artifact_local] +[mypy-weave.legacy.weave.artifact_local] disallow_untyped_defs = False disallow_untyped_calls = False -[mypy-weave.legacy.artifact_wandb] +[mypy-weave.legacy.weave.artifact_wandb] disallow_untyped_defs = False disallow_untyped_calls = False @@ -199,19 +199,19 @@ disallow_untyped_calls = False disallow_untyped_defs = False disallow_untyped_calls = False -[mypy-weave.legacy.client] +[mypy-weave.legacy.weave.client] disallow_untyped_defs = False disallow_untyped_calls = False -[mypy-weave.legacy.mappers_python_def] +[mypy-weave.legacy.weave.mappers_python_def] disallow_untyped_defs = False disallow_untyped_calls = False -[mypy-weave.legacy.mappers_gql] +[mypy-weave.legacy.weave.mappers_gql] disallow_untyped_defs = False disallow_untyped_calls = False -[mypy-weave.legacy.mappers_publisher] +[mypy-weave.legacy.weave.mappers_publisher] disallow_untyped_defs = False disallow_untyped_calls = False @@ -227,7 +227,7 @@ disallow_untyped_calls = False disallow_untyped_defs = False disallow_untyped_calls = False -[mypy-weave.legacy.forward_graph] +[mypy-weave.legacy.weave.forward_graph] disallow_untyped_defs = False disallow_untyped_calls = False @@ -235,15 +235,15 @@ disallow_untyped_calls = False disallow_untyped_defs = False disallow_untyped_calls = False -[mypy-weave.legacy.make_type] +[mypy-weave.legacy.weave.make_type] disallow_untyped_defs = False disallow_untyped_calls = False -[mypy-weave.legacy.serialize] +[mypy-weave.legacy.weave.serialize] disallow_untyped_defs = False disallow_untyped_calls = False -[mypy-weave.legacy.op_def] +[mypy-weave.legacy.weave.op_def] disallow_untyped_defs = False disallow_untyped_calls = False @@ -255,19 +255,19 @@ disallow_untyped_calls = False disallow_untyped_defs = False disallow_untyped_calls = False -[mypy-weave.legacy.execute_fast] +[mypy-weave.legacy.weave.execute_fast] disallow_untyped_defs = False disallow_untyped_calls = False -[mypy-weave.legacy.op_def_type] +[mypy-weave.legacy.weave.op_def_type] disallow_untyped_defs = False disallow_untyped_calls = False -[mypy-weave.legacy.op_execute] +[mypy-weave.legacy.weave.op_execute] disallow_untyped_defs = False disallow_untyped_calls = False -[mypy-weave.legacy.execute] +[mypy-weave.legacy.weave.execute] disallow_untyped_defs = False disallow_untyped_calls = False @@ -275,19 +275,19 @@ disallow_untyped_calls = False disallow_untyped_defs = False disallow_untyped_calls = False -[mypy-weave.legacy.graph_mapper] +[mypy-weave.legacy.weave.graph_mapper] disallow_untyped_defs = False disallow_untyped_calls = False -[mypy-weave.legacy.context] +[mypy-weave.legacy.weave.context] disallow_untyped_defs = False disallow_untyped_calls = False -[mypy-weave.legacy.decorator_class] +[mypy-weave.legacy.weave.decorator_class] disallow_untyped_defs = False disallow_untyped_calls = False -[mypy-weave.legacy.mappers_arrow] +[mypy-weave.legacy.weave.mappers_arrow] disallow_untyped_defs = False disallow_untyped_calls = False @@ -295,11 +295,11 @@ disallow_untyped_calls = False disallow_untyped_defs = False disallow_untyped_calls = False -[mypy-weave.legacy.decorator_op] +[mypy-weave.legacy.weave.decorator_op] disallow_untyped_defs = False disallow_untyped_calls = False -[mypy-weave.legacy.decorator_arrow_op] +[mypy-weave.legacy.weave.decorator_arrow_op] disallow_untyped_defs = False disallow_untyped_calls = False @@ -307,15 +307,15 @@ disallow_untyped_calls = False disallow_untyped_defs = False disallow_untyped_calls = False -[mypy-weave.legacy.decorator_type] +[mypy-weave.legacy.weave.decorator_type] disallow_untyped_defs = False disallow_untyped_calls = False -[mypy-weave.legacy.panel_util] +[mypy-weave.legacy.weave.panel_util] disallow_untyped_defs = False disallow_untyped_calls = False -[mypy-weave.legacy.compile] +[mypy-weave.legacy.weave.compile] disallow_untyped_defs = False disallow_untyped_calls = False @@ -327,7 +327,7 @@ disallow_untyped_calls = False disallow_untyped_defs = False disallow_untyped_calls = False -[mypy-weave.legacy.panel] +[mypy-weave.legacy.weave.panel] disallow_untyped_defs = False disallow_untyped_calls = False @@ -335,15 +335,15 @@ disallow_untyped_calls = False disallow_untyped_defs = False disallow_untyped_calls = False -[mypy-weave.legacy.derive_op] +[mypy-weave.legacy.weave.derive_op] disallow_untyped_defs = False disallow_untyped_calls = False -[mypy-weave.legacy.async_demo] +[mypy-weave.legacy.weave.async_demo] disallow_untyped_defs = False disallow_untyped_calls = False -[mypy-weave.legacy.runs] +[mypy-weave.legacy.weave.runs] disallow_untyped_defs = False disallow_untyped_calls = False diff --git a/pyrightconfig.json b/pyrightconfig.json index a42ea6cf76c..0ad38dc42a1 100644 --- a/pyrightconfig.json +++ b/pyrightconfig.json @@ -19,7 +19,7 @@ "weave/box.py", "weave/cli.py", "weave/client_interface.py", - "weave.legacy.codify.py", + "weave.legacy.weave.codify.py", "weave/compile_domain.py", "weave/conftest.py", "weave/context.py", diff --git a/weave/__init__.py b/weave/__init__.py index 0ed50283b77..4a92a3147a7 100644 --- a/weave/__init__.py +++ b/weave/__init__.py @@ -3,35 +3,31 @@ import sys # We track what modules were loaded before importing weave, so we can ensure -# that someone doesn't introduce auto-importing loading weave.legacy.ops or weave.legacy.panels +# that someone doesn't introduce auto-importing loading weave.legacy.weave.ops or weave.legacy.weave.panels # (because they are slow to import and have more dependencies, and they are part of # the engine and UI layers which should be kept separate from the core layer). pre_init_modules = set(sys.modules.keys()) -from weave.legacy import context_state as _context_state +from weave.legacy.weave import context_state as _context_state _loading_builtins_token = _context_state.set_loading_built_ins() -from weave.legacy import weave_types as types -from .legacy import storage +from weave.legacy.weave import weave_types as types +from weave.legacy.weave import storage +from weave.legacy.weave.api import * +from weave.legacy.weave.errors import * +from weave.legacy.weave import mappers_python_def +from weave.legacy.weave import wandb_api as _wandb_api +from weave.legacy.weave import context as _context -from .legacy.api import * -from .trace.api import * - -from .legacy.errors import * - -from weave.legacy import mappers_python_def - -from weave.legacy import wandb_api as _wandb_api - -from . import version +from weave import version _wandb_api.init() # Ensure there is a client available for eager mode -from weave.legacy import context as _context +from weave.trace.api import * _context_state.clear_loading_built_ins(_loading_builtins_token) __version__ = version.VERSION @@ -44,20 +40,20 @@ from weave.trace.util import ThreadPoolExecutor, Thread # See the comment above pre_init_modules above. This is check to ensure we don't accidentally -# introduce loading weave.legacy.ops or weave.legacy.panels when importing weave. +# introduce loading weave.legacy.weave.ops or weave.legacy.weave.panels when importing weave. newly_added_modules = set(sys.modules.keys()) - pre_init_modules ops_modules = [] panels_modules = [] for module_name in newly_added_modules: - if module_name.startswith("weave.legacy.ops"): + if module_name.startswith("weave.legacy.weave.ops"): ops_modules.append(module_name) - if module_name.startswith("weave.legacy.panels"): + if module_name.startswith("weave.legacy.weave.panels"): panels_modules.append(module_name) if ops_modules or panels_modules: all_invalid_modules = ops_modules + panels_modules invalid_submodules = set([".".join(m.split(".")[:2]) for m in all_invalid_modules]) raise errors.WeaveInternalError( - "importing weave should not import weave.legacy.ops or weave.legacy.panels, but the following modules were imported: " + "importing weave should not import weave.legacy.weave.ops or weave.legacy.weave.panels, but the following modules were imported: " + ", ".join(invalid_submodules) ) diff --git a/weave/api.py b/weave/api.py deleted file mode 100644 index 892dd0ec968..00000000000 --- a/weave/api.py +++ /dev/null @@ -1,4 +0,0 @@ -"""These are the top-level functions in the `import weave` namespace.""" - -from .legacy.api import * -from .trace.api import * diff --git a/weave/conftest.py b/weave/conftest.py index f9916306774..a33d2ac523c 100644 --- a/weave/conftest.py +++ b/weave/conftest.py @@ -13,10 +13,12 @@ from flask.testing import FlaskClient import weave -from weave import weave_init -from weave.legacy import client as client_legacy -from weave.legacy import context_state, environment, io_service, serialize -from weave.legacy.language_features.tagging.tag_store import isolated_tagging_context +from weave.legacy.weave import client as client_legacy +from weave.legacy.weave import context_state, environment, io_service, serialize +from weave.legacy.weave.language_features.tagging.tag_store import ( + isolated_tagging_context, +) +from weave.trace import weave_init from weave.trace_server import ( clickhouse_trace_server_batched, remote_http_trace_server, @@ -24,7 +26,7 @@ ) from weave.trace_server import trace_server_interface as tsi -from .legacy import logs +from .legacy.weave import logs from .tests import fixture_fakewandb from .tests.trace.trace_server_clickhouse_conftest import * from .tests.wandb_system_tests_conftest import * @@ -37,7 +39,7 @@ # tests. context_state._eager_mode.set(False) -# A lot of tests rely on weave.legacy.ops.* being in scope. Importing this here +# A lot of tests rely on weave.legacy.weave.ops.* being in scope. Importing this here # makes that work... ### Disable datadog engine tracing @@ -269,7 +271,7 @@ def factory(process=False): @pytest.fixture() def consistent_table_col_ids(): - from weave.legacy.panels import table_state + from weave.legacy.weave.panels import table_state with table_state.use_consistent_col_ids(): yield diff --git a/weave/deploy/gcp/__init__.py b/weave/deploy/gcp/__init__.py index 3100723a4ce..dfe8947be6c 100644 --- a/weave/deploy/gcp/__init__.py +++ b/weave/deploy/gcp/__init__.py @@ -8,8 +8,8 @@ from pathlib import Path from weave import __version__ -from weave.legacy import environment -from weave.legacy.artifact_wandb import WeaveWBArtifactURI as WeaveWBArtifactURI +from weave.legacy.weave import environment +from weave.legacy.weave.artifact_wandb import WeaveWBArtifactURI as WeaveWBArtifactURI from weave.trace.refs import ObjectRef, parse_uri from ..util import execute, safe_name diff --git a/weave/deploy/modal/__init__.py b/weave/deploy/modal/__init__.py index 947cbb2f057..6972f32ce7f 100644 --- a/weave/deploy/modal/__init__.py +++ b/weave/deploy/modal/__init__.py @@ -5,8 +5,8 @@ import typing from pathlib import Path -from weave.legacy import artifact_wandb as artifact_wandb -from weave.legacy import environment +from weave.legacy.weave import artifact_wandb as artifact_wandb +from weave.legacy.weave import environment from weave.trace.refs import ObjectRef, parse_uri try: diff --git a/weave/deploy/modal/stub.py b/weave/deploy/modal/stub.py index 2dee6e838e6..29242192fde 100644 --- a/weave/deploy/modal/stub.py +++ b/weave/deploy/modal/stub.py @@ -4,7 +4,7 @@ from modal import Image, Secret, Stub, asgi_app from weave.deploy.util import safe_name -from weave.legacy.uris import WeaveURI +from weave.legacy.weave.uris import WeaveURI from weave.trace.refs import ObjectRef, parse_uri image = ( @@ -26,7 +26,7 @@ @stub.function(image=image, secret=Secret.from_dotenv(__file__)) @asgi_app(label=safe_name(uri.name)) def fastapi_app() -> FastAPI: - from weave import api + from weave.trace import api from weave.trace.serve_fastapi import object_method_app uri_ref = parse_uri(os.environ["MODEL_REF"]) diff --git a/weave/integrations/langchain/langchain.py b/weave/integrations/langchain/langchain.py index 9d2ee657d9e..0b974312390 100644 --- a/weave/integrations/langchain/langchain.py +++ b/weave/integrations/langchain/langchain.py @@ -35,12 +35,12 @@ from contextvars import ContextVar from uuid import UUID -from weave.client_context import weave_client as weave_client_context from weave.integrations.integration_utilities import ( make_pythonic_function_name, truncate_op_name, ) from weave.trace import call_context +from weave.trace.client_context import weave_client as weave_client_context from weave.trace.patcher import Patcher from weave.trace.weave_client import Call diff --git a/weave/integrations/llamaindex/llamaindex.py b/weave/integrations/llamaindex/llamaindex.py index acb22efb3c3..df30a6b6abb 100644 --- a/weave/integrations/llamaindex/llamaindex.py +++ b/weave/integrations/llamaindex/llamaindex.py @@ -1,4 +1,4 @@ -from weave.client_context import weave_client as weave_client_context +from weave.trace.client_context import weave_client as weave_client_context from weave.trace.patcher import Patcher from weave.trace.weave_client import Call diff --git a/weave/legacy/Dockerfile b/weave/legacy/Dockerfile new file mode 100644 index 00000000000..e1d677577ff --- /dev/null +++ b/weave/legacy/Dockerfile @@ -0,0 +1,51 @@ +FROM frolvlad/alpine-glibc:alpine-3.17_glibc-2.34 +LABEL MAINTAINER="danny goldstein " + +ENV CONDA_VERSION=4.9.2 \ + CONDA_MD5=b4e46fcc8029e2cfa731b788f25b1d36 \ + PYTHON_VERSION=39 \ + PYTHONDONTWRITEBYTECODE=true \ + PATH=/opt/conda/bin/:/opt/conda/envs/base/:$PATH \ + WEAVE_LOCAL_ARTIFACT_DIR=/local-artifacts + +# We do the following all in one block: +# - Create user and group weave +# - Install miniconda install dependencies +# - Download miniconda and check the md5sum +# - Install miniconda +# - Install tini +# - Remove all conda managed static libraries +# - Remove all conda managed *.pyc files +# - Cleanup conda files +# - Uninstall miniconda install dependencies +RUN mkdir /weave && + apk add --no-cache wget bzip2 && + wget --quiet https://repo.continuum.io/miniconda/Miniconda3-py${PYTHON_VERSION}_$CONDA_VERSION-Linux-x86_64.sh && + echo "${CONDA_MD5} Miniconda3-py${PYTHON_VERSION}_$CONDA_VERSION-Linux-x86_64.sh" >miniconda.md5 && + if [ $(md5sum -c miniconda.md5 | awk '{print $2}') != "OK" ]; then exit 1; fi && + mv Miniconda3-py${PYTHON_VERSION}_$CONDA_VERSION-Linux-x86_64.sh miniconda.sh && + sh ./miniconda.sh -b -p /opt/conda && + rm miniconda.sh miniconda.md5 && + ln -s /opt/conda/etc/profile.d/conda.sh /etc/profile.d/conda.sh && + echo ". /opt/conda/etc/profile.d/conda.sh" >>/weave/.profile && + echo "conda activate base" >>/weave/.profile && + /opt/conda/bin/conda install conda==$CONDA_VERSION && + echo "conda == $CONDA_VERSION" >>/opt/conda/conda-meta/pinned && + /opt/conda/bin/conda install --freeze-installed tini pip gunicorn python=3.9.7 -y && + find /opt/conda/ -follow -type f -name '*.a' -delete && + find /opt/conda/ -follow -type f -name '*.pyc' -delete && + /opt/conda/bin/conda clean -afy && + apk del wget bzip2 + +ENV PORT 9239 + +WORKDIR /weave +ADD . . + +RUN pip install -r requirements.engine.txt +RUN mkdir /local-artifacts + +EXPOSE 9239 + +ENTRYPOINT [ "tini", "-g", "--" ] +CMD ["gunicorn", "weave.legacy.weave.weave_server:app"] diff --git a/Makefile b/weave/legacy/Makefile similarity index 100% rename from Makefile rename to weave/legacy/Makefile diff --git a/weave/legacy/arrow/__init__.py b/weave/legacy/__init__.py similarity index 100% rename from weave/legacy/arrow/__init__.py rename to weave/legacy/__init__.py diff --git a/weave/legacy/core_types/__init__.py b/weave/legacy/core_types/__init__.py deleted file mode 100644 index 9b2fa51859e..00000000000 --- a/weave/legacy/core_types/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from weave.legacy.core_types.stream_table_type import StreamTableType diff --git a/weave/docs/dispatching_and_resolution.md b/weave/legacy/docs/dispatching_and_resolution.md similarity index 100% rename from weave/docs/dispatching_and_resolution.md rename to weave/legacy/docs/dispatching_and_resolution.md diff --git a/weave/legacy/ecosystem/__init__.py b/weave/legacy/ecosystem/__init__.py deleted file mode 100644 index 265d3d5e9bb..00000000000 --- a/weave/legacy/ecosystem/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from weave.legacy.ecosystem.root import * diff --git a/weave/legacy/ecosystem/bertviz/__init__.py b/weave/legacy/ecosystem/bertviz/__init__.py deleted file mode 100644 index 87a014869a2..00000000000 --- a/weave/legacy/ecosystem/bertviz/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from weave.legacy.ecosystem.bertviz.panels import * diff --git a/weave/legacy/ecosystem/craiyon/__init__.py b/weave/legacy/ecosystem/craiyon/__init__.py deleted file mode 100644 index b007a23646e..00000000000 --- a/weave/legacy/ecosystem/craiyon/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from weave.legacy.ecosystem.craiyon.ops import * diff --git a/weave/legacy/ecosystem/huggingface/__init__.py b/weave/legacy/ecosystem/huggingface/__init__.py deleted file mode 100644 index 2f42a92960c..00000000000 --- a/weave/legacy/ecosystem/huggingface/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -from weave.legacy import context_state as _context - -_loading_builtins_token = _context.set_loading_built_ins() - -from weave.legacy.ecosystem.huggingface.hfmodel import ModelOutputAttention -from weave.legacy.ecosystem.huggingface.huggingface_datasets import * -from weave.legacy.ecosystem.huggingface.huggingface_models import * -from weave.legacy.ecosystem.huggingface.model_textclassification import ( - FullTextClassificationPipelineOutput, -) - -_context.clear_loading_built_ins(_loading_builtins_token) diff --git a/weave/legacy/ecosystem/keras/__init__.py b/weave/legacy/ecosystem/keras/__init__.py deleted file mode 100644 index 7e87b9429a8..00000000000 --- a/weave/legacy/ecosystem/keras/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -from weave.legacy import context_state as _context - -_loading_builtins_token = _context.set_loading_built_ins() -from weave.legacy.ecosystem.keras.model import * - -_context.clear_loading_built_ins(_loading_builtins_token) diff --git a/weave/legacy/ecosystem/lens/__init__.py b/weave/legacy/ecosystem/lens/__init__.py deleted file mode 100644 index 68f024d1abb..00000000000 --- a/weave/legacy/ecosystem/lens/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from weave.legacy.ecosystem.lens.lens import * diff --git a/weave/legacy/ecosystem/py/__init__.py b/weave/legacy/ecosystem/py/__init__.py deleted file mode 100644 index a6167c9b4cf..00000000000 --- a/weave/legacy/ecosystem/py/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from weave.legacy.ecosystem.py import pydoc diff --git a/weave/legacy/ecosystem/pytorch/__init__.py b/weave/legacy/ecosystem/pytorch/__init__.py deleted file mode 100644 index 6e0da8b69d5..00000000000 --- a/weave/legacy/ecosystem/pytorch/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from weave.legacy.ecosystem.pytorch.model import * diff --git a/weave/legacy/ecosystem/sklearn/__init__.py b/weave/legacy/ecosystem/sklearn/__init__.py deleted file mode 100644 index 6c3b24135bb..00000000000 --- a/weave/legacy/ecosystem/sklearn/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from weave.legacy.ecosystem.sklearn.datasets import * diff --git a/weave/legacy/ecosystem/slack/__init__.py b/weave/legacy/ecosystem/slack/__init__.py deleted file mode 100644 index 24421beadde..00000000000 --- a/weave/legacy/ecosystem/slack/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from weave.legacy.ecosystem.slack.panels import * -from weave.legacy.ecosystem.slack.slack import * diff --git a/weave/legacy/ecosystem/slurm/__init__.py b/weave/legacy/ecosystem/slurm/__init__.py deleted file mode 100644 index 208a2d8e6c9..00000000000 --- a/weave/legacy/ecosystem/slurm/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from weave.legacy.ecosystem.slurm.ops import * diff --git a/weave/legacy/ecosystem/torchvision/__init__.py b/weave/legacy/ecosystem/torchvision/__init__.py deleted file mode 100644 index f875c477039..00000000000 --- a/weave/legacy/ecosystem/torchvision/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from weave.legacy.ecosystem.torchvision.datasets import * diff --git a/weave/legacy/ecosystem/wandb/__init__.py b/weave/legacy/ecosystem/wandb/__init__.py deleted file mode 100644 index 709aa0d4414..00000000000 --- a/weave/legacy/ecosystem/wandb/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -from weave.legacy import context_state as _context - -_loading_builtins_token = _context.set_loading_built_ins() - -from weave.legacy.ecosystem.wandb import wandb_objs -from weave.legacy.ecosystem.wandb.panel_distribution import * -from weave.legacy.ecosystem.wandb.panel_geo import * -from weave.legacy.ecosystem.wandb.panel_scatter import * -from weave.legacy.ecosystem.wandb.panel_time_series import * -from weave.legacy.ecosystem.wandb.run_chain import * -from weave.legacy.ecosystem.wandb.weave_plotly import * - -_context.clear_loading_built_ins(_loading_builtins_token) diff --git a/weave/legacy/examples/apps/explore_embeddings.ipynb b/weave/legacy/examples/apps/explore_embeddings.ipynb new file mode 100644 index 00000000000..a5e820dc9d7 --- /dev/null +++ b/weave/legacy/examples/apps/explore_embeddings.ipynb @@ -0,0 +1,383 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "d3f10990", + "metadata": {}, + "source": [ + "# Embedding explorer\n", + "\n", + "In this tutorial, we'll create a notebook-based Embeddings explorer. We will use functions, or **Weave Ops**, defined in the Weave ecosystem so that they're available to compatible types in the Weave UI.\n", + "\n", + "You'll learn how to:\n", + "- Save and version embeddings\n", + "- Build an interactive embeddings explorer using Weave\n", + "- Use the Weave ecosystem to: \n", + " - Embed a string column using OpenAI Embeddings\n", + " - Cluster & project the embeddings using umap\n", + "\n", + "Note: we will follow the clustering / projection approach from here: https://umap-learn.readthedocs.io/en/latest/clustering.html" + ] + }, + { + "cell_type": "markdown", + "id": "dd81428f", + "metadata": {}, + "source": [ + "## Import Dependencies\n", + "\n", + "Make sure you have weave installed." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cb252cdb", + "metadata": {}, + "outputs": [], + "source": [ + "!pip install weave openai tiktoken tenacity hdbscan" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "13a3497f", + "metadata": {}, + "outputs": [], + "source": [ + "import pandas\n", + "import weave\n", + "from weave.legacy.weave.ecosystem import openai\n", + "from weave.legacy.weave.ecosystem import umap\n", + "from weave.legacy.weave.ecosystem import hdbscan" + ] + }, + { + "cell_type": "markdown", + "id": "43771ee8", + "metadata": {}, + "source": [ + "## Download the data" + ] + }, + { + "cell_type": "markdown", + "id": "2c74303f", + "metadata": {}, + "source": [ + "We'll use a pokemon dataset from https://calmcode.io/datasets/pokemon.json." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a297c756", + "metadata": {}, + "outputs": [], + "source": [ + "!curl -O https://calmcode.io/datasets/pokemon.json" + ] + }, + { + "cell_type": "markdown", + "id": "81a36bc4", + "metadata": {}, + "source": [ + "## Read the data and save it to Weave" + ] + }, + { + "cell_type": "markdown", + "id": "a7b73421", + "metadata": {}, + "source": [ + "Weave will store and version the data locally under the name you pass as the second argument to `weave.save`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e68f6c86", + "metadata": {}, + "outputs": [], + "source": [ + "raw_data = pandas.read_json('./pokemon.json')\n", + "data = weave.save(weave.legacy.weave.ops.dataframe_to_arrow(raw_data), 'pokemon_data')" + ] + }, + { + "cell_type": "markdown", + "id": "8352ddb9", + "metadata": {}, + "source": [ + "We can show the data by executing it in a notebook, this will call `weave.show` on it." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "614f551d", + "metadata": {}, + "outputs": [], + "source": [ + "data # or weave.show(data)" + ] + }, + { + "attachments": { + "image.png": { + "image/png": "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" + } + }, + "cell_type": "markdown", + "id": "7619577f", + "metadata": {}, + "source": [ + "You can change the type of the displayed weave panel by changing `Table` to `Plot` and it will intelligently change to display a plot of the data.\n", + "![image.png](attachment:image.png)\n", + "\n", + "You've now created a Weave `Plot` Panel, or **Weave PanelPlot**. The default PanelPlot will show the HP (hit points or health points) of all the Pokemon versus their total stats. From the resulting PanelPlot, you can \n", + "* hover your mouse over a point to see the full data for it in the tooltip\n", + "* click on the magnifuing glass in the bottom right corner, then click and drag over a region of the plot to rescale and zoom to the selected region\n", + "* double-click on the plot area to reset to the original zoom level" + ] + }, + { + "cell_type": "markdown", + "id": "356dc2a9", + "metadata": {}, + "source": [ + "## Embed and cluster the data" + ] + }, + { + "cell_type": "markdown", + "id": "4fd47c57", + "metadata": {}, + "source": [ + "Now we can pass a column from this data to another Weave Op to embed it. This will turn each row in the column `name` into a vector." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "43cec2d8", + "metadata": {}, + "outputs": [], + "source": [ + "embeddings = openai.openai_embed(data['name'], {\"model\": \"text-embedding-ada-002\"})" + ] + }, + { + "cell_type": "markdown", + "id": "6715de4a", + "metadata": {}, + "source": [ + "Now, we'll project this to 2 dimensions for clustering, and then we'll cluster this projection using `hdbscan`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5eff5bca", + "metadata": {}, + "outputs": [], + "source": [ + "clusterable_projection = umap.umap_project(\n", + " embeddings, {\n", + " 'n_neighbors': 30,\n", + " 'min_dist': 0,\n", + " 'n_components': 2,\n", + " }\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "583830d0", + "metadata": {}, + "outputs": [], + "source": [ + "clusters = hdbscan.hdbscan_cluster(clusterable_projection, {\n", + " 'min_samples': 10,\n", + " 'min_cluster_size': 60\n", + " })" + ] + }, + { + "cell_type": "markdown", + "id": "87c4ad19", + "metadata": {}, + "source": [ + "Then, we'll project the embeddings again for plotting. For efficient computation, Weave isn't actually executing anything until we call `weave.use` or `weave.show` on the built-up computation (or \"compute graph\")." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "21f5917b", + "metadata": {}, + "outputs": [], + "source": [ + "projection = umap.umap_project(embeddings, {})" + ] + }, + { + "cell_type": "markdown", + "id": "e3b7f76c", + "metadata": {}, + "source": [ + "## Combine data for plotting" + ] + }, + { + "cell_type": "markdown", + "id": "1d017836", + "metadata": {}, + "source": [ + "Finally, we'll combine our data into one big table so we can display the Pokemon name and types alongside our embedding. Here, we're calling `weave.use` on each weave object to execute the computation, and then we're merging them into a new list of dictionaries. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d642110d", + "metadata": {}, + "outputs": [], + "source": [ + "weave.show([{'x': x, 'y': y, 'k':k, 'd':d} for (x,y),k,d in \n", + " zip(weave.use(projection),\n", + " weave.use(clusters),\n", + " weave.use(data))])" + ] + }, + { + "cell_type": "markdown", + "id": "1cf9b267", + "metadata": {}, + "source": [ + "## Build and publish an interactive dashboard" + ] + }, + { + "cell_type": "markdown", + "id": "202f5f66-a3c4-41ea-b1ca-cef26f98f26b", + "metadata": {}, + "source": [ + "Some Weave UI tips to build an interactive dashboard in-line to explore and get insights into your embeddings:\n", + "* **add new panels**: click the + button on the drawer that pops out on the right hand side of the cell\n", + "* **change panel types**: as we did above to change the `Table` panel named `pokemon_data` into a `Plot` panel\n", + "* **refer to other panels by their name**: add a new `plot` panel that uses `table.selected_data` from the first panel. You can click the pencil to edit the configuration of your `plot`. " + ] + }, + { + "attachments": { + "image.png": { + "image/png": "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Y29tL2V4aWYvMS4wLyIKICAgICAgICAgICAgeG1sbnM6dGlmZj0iaHR0cDovL25zLmFkb2JlLmNvbS90aWZmLzEuMC8iPgogICAgICAgICA8ZXhpZjpVc2VyQ29tbWVudD5TY3JlZW5zaG90PC9leGlmOlVzZXJDb21tZW50PgogICAgICAgICA8ZXhpZjpQaXhlbFhEaW1lbnNpb24+MTY3MDwvZXhpZjpQaXhlbFhEaW1lbnNpb24+CiAgICAgICAgIDxleGlmOlBpeGVsWURpbWVuc2lvbj43MDQ8L2V4aWY6UGl4ZWxZRGltZW5zaW9uPgogICAgICAgICA8dGlmZjpSZXNvbHV0aW9uVW5pdD4yPC90aWZmOlJlc29sdXRpb25Vbml0PgogICAgICAgICA8dGlmZjpYUmVzb2x1dGlvbj4xNDQvMTwvdGlmZjpYUmVzb2x1dGlvbj4KICAgICAgICAgPHRpZmY6WVJlc29sdXRpb24+MTQ0LzE8L3RpZmY6WVJlc29sdXRpb24+CiAgICAgICAgIDx0aWZmOk9yaWVudGF0aW9uPjE8L3RpZmY6T3JpZW50YXRpb24+CiAgICAgIDwvcmRmOkRlc2NyaXB0aW9uPgogICA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgpZQ7xWAABAAElEQVR4AeydB2BUVfbGT3ovkEoJhN57B1FAEHsHe/evrrqra9t1177uuuvaXdvae1lRERRBivTeewuhJZDee8L/fDe8YZJMwkxISPuODvPmlfvu+81M5r77neJ2TE1oJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACzZ6Ae7O/Ql4gCZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZCAIUBhiB8EEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEmghBCgMtZA3mpdJAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAhSG+BkgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIggRZCgMJQC3mjeZkkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQGGInwESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESaCEEKAy1kDeal0kCJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACnq4gSE9Pl7y8PFcO4b4kQAIkQAIkQAIkcMoEMo+5ya4St1Nuhw2QAAmQAAmQAAmQgKsEov19pKt7mTksPDzc1cO5PwmQAAmQAAmQAAk0OgIuCUMlJSVSWFjY6C6CHSIBEiABEiABEmjeBHLK3CSpmMJQ836XeXUkQAIkQAIk0DgJ+Hl4SJFHSePsHHtFAiRAAiRAAiRAArUg4JIwZLXfuXNna5HPJEACJEACJEACJFCvBOLi4uq1fTZOAiRAAiRAAiRAAiRAAiRAAiRAAiRAAi2JQK2EoZYEiNdKAiRAAiRAAiTQuAh0DPSXyTGRjatT7A0JkAAJkAAJkECzIrA/J09mH0xqVtfEiyEBEiABEiABEiABi4C7tcBnEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiCB5k2AwlDzfn95dSRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRgI0BhyIaCCyRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiTQvAlQGGre7y+vjgRIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARsBCgM2VBwgQRIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgASaNwEKQ837/eXVkQAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkICNAIUhGwoukAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkEDzJkBhqHm/v7w6EiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABErARoDBkQ8EFEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEmjeBCgMNe/3l1dHAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAjYCFIZsKLhAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAs2bAIWh5v3+8upIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIwEaAwpANBRdIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIoHkToDDUvN9fXh0JkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJ2AhQGLKh4AIJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJNG8CFIaa9/vLqyMBEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABGwEKQzYUXCABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiCB5k2AwlDzfn95dSTgkEBOTq5s3rzF4TauJAESIAESIAESqB2B3Nxc+Xba9zJh4vnyyJ8ek+ycnNo1xKNIgARIgARIgARIgARIgARIgARIoB4JeNZj22yaBJwm8JfHnpIVK1bJxRedL/ffd6/Tx3FH1wls3LRZHnviGYnt2EFef/VF1xvgESRAAiRAAiTQjAkkJSXLmrXr5NixY7ardHd3F39/fwkPC5OIyHDzjHWVDccUFRWpIJQt+QV5oo1U3qVFvs4+XCT7fs0UTx83aT8mWII7eDvNIS+lRA4tyZKsg0UVjgmJ9ZGuF4SKm7tbhfUt5UVZaYlk7NslB5fPq3DJPkEhEtlvmLTu0qvCer4gARIgARIgARIgARIgARIgAXsCFIbsaXC51gT27I2TnTt3VTjezc1NgoODJSIiXKKjIiUkJKTCdvsX+fn5ZhKloKDQfjWX65BAWVmZvP/Bx/KePvStkaumXlFt6198+Y3s2btXzjxjjIwbd6Ys+G2hLF6yTLp26SLXXjPV4XGztu+TLYkpVbb9cdwQ8XQweVZlx0a+IqugSN5ZtrFKL4d3aCNndW1fZT1XkAAJkAAJNE0Cu3bvkWeefU5KS0sdXoC3t7f07NlDrrz8Uhl31ljx8/NzuF9dr8zNzZMdO3dKcnKK9O7dUzrExNT1KeqtvcKMEklYli2+YV7S/oxgl85TlF0qR9fnSdJGFdrsLHpIgHQ5D8KQ3coWtHhMx3W5SQkqDM2tcNUBEW0kIKo9haEKVPiCBEiABEiABEiABEiABEigMgEKQ5WJ8HWtCCxevFTefPu/1R7rpnftZ4wZJddcPVWGDR1c7X71sWH6jzOlqLjYTOBArGqp9qdHH5ffFi6Stm3byt//9qT07dO7WhSrVq+RpcuWS2RkpBGGdu7aIzNm/ixjRo+qVhiCKPTL9vgqbf7hzMEqDFVZ3eRWFBSXOLy+EF8fCkNN7t1kh0mABEjg5AQQHTRixHCJjo4yIhHSxCUkJMrOXbtlk0bf7tsXr0LNLvn9PXeJp2f9D6lT01IFY5qtW7fL7bfdXGfC0LGSY1KQUSr5aSXi5ecuQTHOR/OcnGLVPVwVckI7+cjYp084YCSsypXl/zhcteEWtsbDy1tiRk80D1x6ztHDsmvml5J5YE+jJlFSWCB5KUekTMfmgdHtxNPXv1H3l50jARIgARIgARIgARIggeZKoP7vYpsIuQ0bNsj48eNNb3eqNyYmxF2xq6++WmbPni233nqrvPhiy03PhaigK6+4zKBDhEpGRoYcOXJUVmtKlsVLlsqSpcvk+eeeNWKDK3xruy9Sujz3rxfMhM4Vl12ikTJNUxhCJprcxGLJSy4WpE7xCfFwCcms2b8aUahVq1by37delyiN4KrJrPQ4Hscjfaxna72jYx+eMEzwgIH7xDe/dbRbk10XGeQv8+6ZYuv/Ryu3yqdrttleN6cFvH8JmblyNDtXOoeHSqifT3O6PF4LCZBADQSysrLk888/l7/85S9mXPThhx/WGPFbQ1NNflN4eJhccvEFMmjggArXkpaWpnWEfpAPPvpUFi5cLJ1iO8qll1xUYZ/6eFGiDgq5eRWjZuriPJkHimTj+0lSqOJQr6vD6l0Yqos+s42mSaBURaHDqxbKlq/flqh+I2TI7Q83zQthr0mABEiABEiABEiABEigGRCgMHT8TSwp0TzdKmLAMCnqquVocWEcn1cPN+yu9qUh9w9VYeiuO26r0oXMzEx56pl/GGHo8aeelWn/+1wiIyKq7FfXK5B2pbpUMHV9rvpsLzexSGbfHS/u3m5yyeddXTpVRkamvPTya8ab+V/P/e2kohAa9/Ep9xb2rvRsrXepA9y5yRE4nJkjN33+i3h7eMiP/3dpk+s/O0wCJFB7AnDqKCgoMGMaRMjUNCbCNox/8IiOjm6yzheu0mrdurWcO3mSHDh4UBAxjaih02HFOlbN03FNXduxsmMavXFMIzfcxC+MtwZ1zZftnSBw7FiZlJUUC6KdfII1DaCOM2gkQAIkQAIkQAIkQAIkQAINQ6DJ3f1t2rRJlixZIkOHDpXhw4c3DDWe1WUCiCT6x7NPyeTzL5X8/DzZuHGzTJo4weV2XD0AglRzsNKicrEyINJL3L1ci3p68eVXzQTfHbffWsXruTo2AQGBZlNgpWdrfXXHcX3zIFBYUl5XIyrYX7w8mkEewObxtvAqSKDREIAQhCjp559/XjZv3ixwronRejeInr7//vu1tmDtHT/gzAFBqrCoyKnr9dSJ5YCAAEHdn9NpQUFBEtuxo8yeM89ERhdpf13pQ2pqmixctFiWL18p+w8cVIcMH+nXt4+MHTtGRo4YVkFkS0w8IvPm/ybbNaJ9r6avKyoslB80pdzadRvMJXfsECMXX3R+rSO7dK5eSjWdnIeOL7wCXP+bj+MhLrl7ujY+gR9WmZ4XqeXcPVw7trbv9bFSFcH0J85DHW1Oh5WVlkhpUaF4evu6JIKgftAxPdbN06vCZ6E++3xMv3vHFI67ijYuG95MJ6LycV0QhnBdnn61SyHXEGxc5sEDSIAESIAESIAESIAESKAJEGhywtCPP/4ojz/+uDz22GMUho5/wOCxm5Ka6vTHzU3cBOlRTrehOHPXrp11EmmL8a51VRhC5MsXX30j27ZtlzidGGnXto0p/jxFU9d10EkRe4vfv19+njVHDh06kYP+H//8t+5SPhHQs2d3U3PI/hhXl4tydPLqaHlqt8oTGiV5ZXrDqzMd1c076P1zSaHu4+vcBAz2hXkHu+ZZuW3bDvlF08hhwmnqlMudvkTr8xERUf45OfEc7nQbtd2xRCcNYJ7H09i50g7ks+qQ19QOhBAfT9fY1tRefW0DGw+deDmVlIi5RcUS4O1VYxctYShU6yfRSIAESMCeQHZ2tnz11Vfy8MMPq6NHvhGEwrQWz9atW+WVV17Rujv75OWXX9bo1Cj7w5xehkjy1tvvmvSnzhzUKTZW7rrzdpkw/ixndq+zffB32N0I58cE/yHSyhmD8LV16zZ56533ZI2m2bUM7e1Q4WfO3HlywXmT5Zabb5TQ0BCzOUGFoW/+N00Sjxyxdpd169abB1YMHjRQzj57nEvCEMYvcb9kSPLmPCnOLZMcTVeLH9BFjx8UOKHAMOaIOSNYOk4INq/t/8lLKpH4eZmSuEYjxhKKTcSRr0YbRfb3l67nh0qI1gWqzjB+ip+XJbump0n2oSJT1yisp690mhQqbUeoY0ptfsirO5muL0gvkQO/ZcmBhdna13LBMbijj3QcHyyxE0PqXCTKT0uW/Utmy5ENK7RuU5IRhjy8fSS4XSdpP2KctBt+lsO6OmXIXhC/S+LmT5eMfbukMDtTvAODJaLXIOk04SIJielcw1XWblNhZrocWvWbSe+Wm1Q+Zg5q21Haj5wgMaMmKhvHIhEEpKzD++WAXmd63E7JT00S76AQadW5p0T1HybR/UfYhDDcp6Tt3Czbf/hYivNzJTc50XT2wOJfJHXHRlvHQzp0ke4XXKPpklvZ1lkLDcHGOjefSYAESIAESIAESIAESKC5EmhywlCqCwJIc33TKl8XUr6cf2F5XZ/K2xy9hkiwZOFcR5vqfZ3H8ZQRpXrz64rBo/bpZ58T6/3HBEpycrJs2LhJfpg+Q+7/w71yxeWX2JqMi4uXDz/6xPYaCyjYbFlm1pm1FoaSt2jE0/vJkrm/UF1d1etUU69gImTIPdGy56d02Tc7UwozS2XyW50ksE3FCfjDy7Jl98wMydxXKCX5ZeIX4SlthgVKvxvCy4Ukq4P6XKppXRY+esCsSd+j51JL3ZYv8x/ab5atf0Y81FYCoiuex9r2m3ojwy48/zyXJoxQaBtmTeydeI406+v6HwgR0zbull+275OErFzTfNvgADm3Vye5YkC3GkWbPK258OXaHbI5IVn2pGSYCJfe0WHSv22EXN6/m8OIFwhIP22Nk/m7D8i+1EzJKiiSEBVBOrYOlhuG9pLBMbWb0KxrLmjviNb5+VjrGe1I0smbjGxx189+lNY7Gt2prdwwrHe1Is+7yzfLukNH5apBPWRgu0j5YMUWc735yqu1v6+M6dRO7hjTX/y9yn8GinSy8r7vFphL2KXngm1OTJHf/a/i34rHzxkpbUPKI8rMTvyHBEigxRDABO+KFSuMc46/v7/8+c9/lkceecSI1UuXLpV7771Xfv75Z/PbAXGoNhYUGChDhwzSKCDnognCw8M1jd3p/5udo1FNhw8niK+vr0RpXUo8O2O7d++V1998RzZt2mycW2695UYZ0K+fqR0065fZ8uVX/5PpM36SYv1dfOiBPwhq+3Xr1kWeefpx4xjz6WdfmnXnqXg0fNgQc8pgjV4KV3HOFUOAB5xOilQUKi3Ar6KaPpXqcAPrYG4aAYSxiL0h4iZ5a76pSZRzuFj8Iz0lQsdAiBjKjC+U+LmZkrI1T3pNDZMO46oKSkVZpRI3K1MOLslW0cNdwnv7SYHWNjq6Pk8y9hZK5oFC6TVFr6WOxKH0uELZ/GGypO8ukNDOPtL1wlApUuedlM35svnjZEnblS8D74gSL3/nHHXsWThazjocL9u//1jSdm+RwOgY6TB6khFMshMOSNqerbL12/fk6Ja10ufK21SAa2NrojgvWw6t/E12/fS1Rhf5SGTfoea4rINxkrhuiWTs3yU9LrxO2gwebTvmVBcyte3t0z6UdG07JKaLdBp3oRQX5EnqLhVxvvtI0vftlL5X3anCXcXvYnFejhGSds/6RrDs1ypcQjp2lSIVso6sX6bXt9qIRT1U5PE4/r0oUyEJohCipyxDNBXWWVai9Ycgsla2hmBTuQ98TQIkQAIkQAIkQAIkQALNkQCFIQfv6ql44ztort5XeXh6ygXnn+v0eTw1fUNDGLxk9+6NM6fu0bOH013Yo8f88aE/mVpBQ4cOkXt+d4d0795NDqpX8edffi0zZv4s/3z+BRU+gmXi2eNNuwMH9pc3Xn/ZpKz773sfmHWvv/qSTqaUzzS0alXVG9GZDqXuyJelzx7WtCdu6tkaosKPt/G2TVyVKzNX7TVNxJwZJH6tPStk1MAEzJrXjsiBBVkS2NZLOk8OMfWCkjbmSdzPGXJEPW4n/LujeklWjFqxJmfs+1Z5HVK4VGfLlq0wm846a2x1uzhc36dXT1NIu61GZcHwjMLaWF/XVqZwnp2zQpbtSzCTi31U1IFtPZIq76/QyYmjqfL0eaONIFL53FtUuHhu7io5omJSsK+34NgCnUzbnJAiK+ITZbm2+bcLxkjQ8VpJOB5C0mMzl8hGFZJC/XykV1SYBPp4yZ7kDCOEPPzjIrluSC+5dWTfyqc77a8hXL04f61AtOkcFiITu3eQ5Nx82a19/d+GXfLrzv3y36smSViAX5W+JWblCAQesHlAvYEPad2g7hGhEhHoLzuT0mTG1r1SrBM1D08YZjs2t1A9xitZ5XX4LNNIgARaJgE4ZCxbtkyysrLkyiuvlAceeMCIFKAxYsQIIxjdcMMNsnbtWtmzZ49GCXd1GVRERLhcNfVKl487nQcUaiq3TZu2yNx5CwS1Fbt06ezU6dO1FuVS5bdhw0aTNu7RPz2kok85o9atW8mN118rbaKi5Z//flEZrpNFWr9onP5+Q/gZOKC/RiUdEx91YvBw95AunTupMDTUqfM62ilQHUoGqSACg8PL6leOSECUlwy7L1rFnurHiW6a8s1HI4ki+vhJj8tbS8zYIDMmQjvFKrgcWJglmz9Klvj5WRLex1/81QHG3rI1YqdQI4a6qUDTU8UjCDKIINqv+2/7MlUSVuZo2/56bNXfNft2nFnOSyqW+DkZJiqpx5WtpPtlrW0p67LUuWfzpymSsqVA9uvYrOsFoc40WeM+SHGWp9Ewabu3SmSfoTLw5vs14uZ45JT+eOYmJ8jeOd9LQWaaeNiNxVF3J10jhPbMnqZRRR2l79V3SVCb8kh4iCWJa5fItmkfyIFlv0rrLr0cRtTU2DEHG/M0wufAkjmSc/SQdJs8RbpMuswW4ZOtkUDbf/zUXMfhlQskdtwFthaQBi552zrZMf1T8Q0Nk95X3Cpth51pxm9IRZdxYI+KW18ZcSikYxdpN7R8W0TvQTL+6bfVcSpN9i2YIQdXLJBO4y+UrpNr/q43BBvbxXKBBEigyRJAits4jWCGeenf206dYnXphMHRJSEh0dTVhYMJjQRIgARIgARaKoGKd2uNmMLbb78tu3bt0iK/5REQM2fO1JzuR2w9fvLJJ6V9+/a21xs3bhSknduwYYN5IEqmf//+ct9998moUaNs+zla8PLyMm2/9dZbsnDhQpMWZdCgQTJhwgTjDQvvzdrY0aNHTXoVeNtiwqR3794ycuRI420bqB6ytTVv7e9TT/y1toeftuP+ox6ySEHjqf0d0M+5SXcM2v75rxdsotDrr7wgniqEwTAR88Rjj5qc/tO++0FefPk1GT1qhMCLubUKP5gwyc8vsF0fPGtr+95ZjWz9PEW9YkXO/Ft7ad3N16zufmkrwfod/0uT3teESa+rqnrtIl0LRKF2owNl2B/bmDz+OLjPNSIb3kuSvT9lyBadoBhy7wmvZ+T6P1ejjmCHlmbLyn8nStuRgTLqz23NOmf+OZyQYHbrEHPiu+HMcZio+uujj9h2hQe3/WvbhjpYeG3heiMKdQkPlecuHKsiRznXtLwCeXTGYrMN+9w/bnCFs2H7X39aIrlFJSZy5kaNnkE0DQzp0t5ZtslEBX2nkUg3De9jOxYRMm004qV/uwi5XqOD7FPWIeLofo2a+WrdDpmgIkysRhA1pHVqHSKdw0PkvjMHS1cVdSyDNvO8CmJzVBh6e+km+es5I6xNVZ4RiRWsE4nvTJ0onVRcgkGM+/uclXL14BNCn7dG831y/Xlm+8I9h+SZ2cvljM7tjChnVvIfEmjhBJAi7YsvvhBEvt54441mIvT77783tXYwPmnXrp1MnDhRpk6dKp07VxUKkGosPj5eZsyYIatXrzap11BLB2OTSy+91NTnsX7fLNTWObEe50RKVoxLML5Zs2aN6cvkyZPljjvu0EmP8t8L61j758TERPnuu+9MNA/GH6gBdNZZZ8mUKVNk4MCB9rvWuHzgwAFzvdHR0TJmzJgKNXUwdkIfhgwZIthvzpw5tRKGauzAad5YWFhkUtIGag2j0tIyydbaSgcPHpTlK1epQLZS+btrWuNhcslFJybOa+oi0tvOnb9QWoWGygg9Dr+19oaooz59e8vokSNk6fIVMl/3hTBU34ZxTZkGciMtrrvXyce4IbE+Jsqmcr8g8rTTcUp+aokcXJRtnF46n3fitwv7Q1gK76Wi0hWtbVE63oEeJoVc9sEiObg420QTnaowdExrFyHa+qCOn6IGBEi3i06IQuhHUIyPOumEyqpXEuXI2hyTVu5Uo4YgDJUWF8uxY1qrx0S+lI9JcD54CwVEtpNeV9xiImt8W5+YiMxLOiKHNVrIyy9AYs863yYK4TBPH18J695XogeOMpE8Rzatko5jJ2NTrQ21izJVwEnQSKSIngNNmjq34xH9aDRQRamOY86RDR+/Iklb1mhaufG21HeIfIpf9ItGMwVL54mXmLR4VkfQRqtOPaSfClvZCfslqt8JxxNrn/IaQ1o7Se+l3O3EMWt75efTzaby+fmaBBozgWL9ezPn13kSoPe+48ad2aBdxT07nB/y8/IlLKy101G09dVp/H7/OneBaR6/4Z06xZpl659Fi5fIlq3bzUv8zvbp3cvaxGcSIAESIAESqJEAatnv2LlbHff6mbIdNe5cw0Y4KBxRnQBpwRvSmoww9MYbb8iWLVtsrCzBx1rx+9//3ghDmHRB3vtp06bpjVm5azvEAEzIbN++3UyMfPzxx3LNNTojX419++23Jj1KWlqabQ9MckyfPl1++eUXk1s/ONi1CWMUab7pppsE4hAM+fh//fVX8/jmm2/khx9+0JQiJyZpbSduYgtFOkDdvXuP6TUmn9O0uDJy4yOqZ+u2bWb9g3/8g4lAcebSkCpuo6Zbgf3ujtttopD9sXf8360y86dZkpKSIj/Nmi2oOVQfhgLJqTsLJHKACk/HRSHrPPBC3TFNc+Ufz11vrcdzcU6ZEY68NRpoyL3RNlHI7KNzBv1ujDCTIPHzM6WvppSrHDWE/UqLyj/Lnpq2zllDIWwUCMeEYps20c4edlr3Q1QQIldQ8+bJc0fZRCF0AunOsO6ub+aafSb17GgigqwOvrF4g+RohAvSxd1sJ/xgO9p7YNwQiW0VLJdpKrrK9vCEoZVXmdddVZz608Th8tSsZfLDpj1VxCiHBzmxcuX+RPlm/a5q97y4bxc5q2tV8Q5CzutXTKhyHD4FD+o1xKdlmfRwd58xQFopL0cGAe2ly8ZJTGiQbTMENIhJlpBm23B8waox5KsiGo0ESKCcAKJl8Fudl5cnGAziNxyRMZbt2LFDfvvtN4HjyvPPPy+jR59I+YQxxOuvvy4ffvhhhZSoGKfs3r1b5s2bZ4Sef/7znxUmU6xzIpIZzi/YD2MY+3o2mzZtMkLTm2++qdG03a3umGd4zK5atUr++te/mr5B1IKjDMZKSP2G8Q7qNl5//fUVjqvuBX5nN2/eLH369JFhw6pO+iLt6PDhw41DDvrV1G2fcnpG09g6statW8tFF54vV0+9ssJ75mhfax2EQIh0iMIdoBFAjiwiPEwGaNQzUsGmpeu4Qh1qgjRiqD7NCEPFZTpZr+JQ9cFCTnXBJ9RTwnr4mpo++Vrbp7IhojpKx1HeQRUjpBFZFDnIX/ZpKjpE+pRpCjt3dZCprSG6GmnpPH3cTX88fCq2hWv1CfVQEcZbSvKOGTHLy99xPR1n++Cu462AiGgJahcrKds3yMbPXpNu507Rc3QwwhDagfiDh70V5mRqpM1ejTYPl7BuVR2nvAKCBHV/jm5epfWSku0PrdVycV6uZCceMCnrIOTYopqOtwbRxic4VCPH2mhquXzJT0/Va0C6QK2tqRFR6Xu3m1R37Yad5fD8/uFRgocjQwRQqUYduWnUm4fnyXmfbjaO+sx1JNAYCUAUwv114pGj4qoDYF1ez9GjSbJMHRmSk1M0BeqJv/lBQYEmsnXkiOHGiaUuz4m20tMzJC8/z9znIp2rq3ZEuVkGZ2MKQxYNPpMACZAACZyMwN64faZW7MGDh/R+8LxaiUMHNAPWz5pGHPf1vTUzk7NpyU/Wt9psbzKzfu+++66Z5L7rrrs0Hdleue666+Tmm2+2XbPlnYtJ8J9++slMWiDFCSJykMoEEzY4FilQsP6yyy6rFjw8bzHh8f777xuPWog5n332mfz973+XWbNmmVQpr732mu3cJ1tAQeYLL7zQvOEPPfSQPProo4IJBUz44PUnn3wit956q+nbydpq7Nsx4XHtDbc47CbEsJtvut6l2j579pSnZ4N3c//+VW+WcSJEB0FhXb5ipUZile/vsAOnuLKkoMxMVCCFSmXz9HU3xYuRO7+ypWr+eohDKN7syBsVkxUQmo6s0Rv1w0UqDPlVbkJz/qs7r5qHTnA4a9bEIVIHnmqklLPndHW/bSoMwc7s0l7aOahbg1o22DZLaw9hXyvNXJ5GBP2256AgyuXmESeigSqf/3IHolDlfSq/HhXbRnz178hhTb1WV5aaWyAbDidV29xIPaerhkinscpmV3K66Wt1wtC4rjEVRCHrPNWJQtheePzGztez6mfdOp7PJNBSCWxTJweMKXr06CFwWpk0aZIgWgYROS+99JIsX75cnn32Wfn000+NEwg4IfXazp07zW8/xiDnnHOOGZscPnxYEJ2MqOi5c+eaKJuLL764Clo4xtx///0mYugvf/mLXHDBBYJIY0Qwvfrqqyb6CCIPttkbxBmMOSBgIfXbE088YcZHSUlJRqT617/+Jf/5z39MBPPgwRWjMu3bwTJ+UyCKweEAkUuRDiZirPVItZaenl65CadeQ8yC8Jav9ROdMYz7QjSdm59G29S1BaiXcWxsRyPM4G8mXrdpEyWdNTJq0MABTju5oF+YxEtTJhCHfFWcC9PUcY4MNwVIKwcDb0xe1b8wdEwjho6ZlHAe3s6PMzC2Sd2Zr7WBCiRPo4SQSg5iDsZCRdllUqi1gyqbp5+71lesqj4hRa+Xv4d4B3jomOeYplsrVXGh9rcpGLMhcqlE+4So6xSt0VjZ0N+85BLxbe0hBbpvcMzJhYrKbVR+HdKhi/S54jbZ/sMnkrBmsRzVCJ+QDl0lZuQEieo/XOsqVXUsK1HxpSAzVceYmt7uq7crN6mCjF5LeorWhCqQ/Iy0KttdXWHaSdNJXJ1URdRQ2t5yxy37drCtQPfxCWml7yOEoRhBP/M1BZ2nfkYh/FQWlOyPr24ZtajK9DtuIob07+bJ7HSzOVl/uJ0EGgMBe1EoXKNzJk08+7R3C44t6zUt6spVq3V8UO7AaN+J7OwcrQW8WQ5qpOw52j/rd81+n1NZXrpsuezXSbVQTSV/3bVXu9wUMotA0IKzzID+/Vw+ngeQAAmQAAm0XAJwJkBd+6N6Tz1j5iyXxSFLFEK5lTGjR1WrTZwuwrW/4zpdPTx+Hgg8MOvmGOlKkLKlssGjdsmSJSY1iv1keD8t7ItJm5iYGJMmDmlYkO7FkT344IPy3HPPmYkebEc9GkzyoL2//e1v8t577xkPW4gVztjdd98tmOhAkWa0axmOh+cvhCNMJKFg8/nnn29tdvoZAzMrZZgzB7mJm6a8aevMri7v4+vrp+zLvWDhmxmq6VLaarRKx44dZML4s1xWUqHEwtqpd21NBu9bWNzx/Wvat7bbkOokqL23oC4QJhwgBlmWtFEL6mrx5jAtolzZsg8VmVUZ+wplxb8SKm82r7MOlO+ToxFHKMRc2U5EDJ04Z+V9Kr/G5BImsTAJlZKSKuHqhdzYzBKGutmlSavcR2ybpZH+1r7YHpeaaXbr0CrIRAdVPsbZ12gTUUtHs3IlPb9Q6za4ab0hb61TVCKo0VNXNq5bjAxqX703G+oj1WRIjbc0LkESVKw6mp2rNYfKxNfLQ7Yklgtridr/vm3CHTbRsRbp8Bgx5BAlV5KAIYB0pfitxu+5fQ2de++914wxbrvtNiMCIboIyzCISHBwwe810rBZht9IOK3s37/fRPQgKtmRMAShBVE6EHKQ1tYyOJVAXIIIhfR0aN+qk4joHjizYHwB55R33nnHCFM4Fn3AsRAeIAx9/vnncjJhCH3I0DQxMG9vb3EUOY31GKdhzAMBCcdg0sUVO6CeV2+9/a78tnCRU4d1io2Vu+683YwxnDrAhZ1i2reT+35/txGBXDjM4a5gghQ7MA8Pz2pvABDVBeEIhmPy8qsKGmZjXf6jc3r60dFwIf3fiTuDouxSI7bs+kGFriMa/eHpphFA7mZcBFGnWGsGQWhC7aDKhshn/zDHJ0EKXa9Ad03HVibFuXrsKQpDBWkqDGnEdXZiseQmnfBkt++Tm/o/YHxXdVrTfi/nlxEJ07prbxl256NyZNNKOaQp4tLjdhjxxWdGqLQfPt6kbvMNaW0ahUhSnJulkeGFUqRvAvatznwCQzTqvObxQnXH2q8vLSrQWj/pKkQVSd7RRCP22G+3liHeePkH2thAUCpQkcjdy8dEFFn7ufZc/mFD2276PajJGoJNTf3hNhJoDAQqi0KXXHyR/p649jtbF9exeYvOX6xYZWsK9/sddJ7FP8BfkpKSTdr8Ak3nlqrZQ6b/OMOINxgj1JXl6P3tqVinTrFV0sudSns8lgRIgARIoOUQwP0tIoUgCrkqDlUWhVBHtqGt5hF5Q/euluevbnIDKU6QSx+TJ0jdUp0hFR28fysb0tVhUiZfb9IhLFkTPpX3s3+NVC2LFi0y4tJTTz1lv8m2jAilO++803gL10YYKlCv2suucN5TBh/iJQvn2s5flwtRkRGCOkB1ZfA2gkHgqMmQOxiWpSlX6tM6jAuWrVoLaOmzh6XPdeGah91bkjfnyfq3j2o0jwpuIwKrnD5HJyRgeSnFeiNedZLEOsBX06uYiRlrhd0z0rzAXE2r0kYn//ZohN3BQ4capTBkCTz2ac7Kr/TEv9Y2a19sOZhR/j5HB9f8uTjRSsWlzQkp8uaSDSbaBluCVAyCOFOqHm+ZBYVmZ6RgqytDXSM8XLUS9RJ+R2sIIWIqv7hEvLSeRaifr0ZKuZvXVh+tZ0ftRwb6O1pd4zqkgYQhIotGAiRQkQCcOsaPH19BFMIemPBA7UBsQ30dOKlY4wSMKTAGcWSIvEFdHjiHIOrWXtyx9ofTC9LR2otC2IZIXNQowjFWijuITTDUEkIUEcQrRFkjUtne0J8zzzxTkIIOqXZxPESv6gxjDSvFLsYRlqOO/f6WMIR12B9RQ/ZCmP2+1S0Hq7A0cuRwp72Lw5VBfTm7VNfH2qyHswY+OxCFCnWCPTUtXYXE9lWaKjDRVuUCHN6P2qTIqdLoSVYgrZrO06vbkJpGRtVkEIX2zc2S7V+nSEhHrd95a4RE9vMXRAJZdnR9rqx4IdF6WeEZ45lSTRPnyEpVTCrWFHD+GlHkE+T6b6Z9m0ZkCnDXtG7eJk1vm6G1Gy/Yt+nKsndQiHTQOj3tNVIo58hhFYjmmwiiffN/lJyEA9Jn6u16nW20rpOHqd8DwSd64EgZdPMDrpymVvuito+Xf4AERGnNo0tu1PdvqFPtoI9eGvEEEaswq/wz6tSBFXZS9zQVz2An+ag1CJsKXeULEmhkBBqLKIT6PavXrLXRGaROoaNHjbS97tWzh0bh9DUTZrg3z9Oav+vWbTC/7badTnEhN+fUhKHanh7e3XDgcNYQbQ2HHctpx9njuB8JkAAJkEDjJlAbcagxikKgfGp3XY37fTK9w2RJXFycqU+EvPjIIQtD8WBXDTf0yN+PlC7x8fFOHb5x40azH4o844PjyLp06WJWI0VebcxD06hcftklTh+KtCtNxTppChdYgk6W1WSoYwSD53B9Wk8tlgxhKGVLvix89MRnCN6yY/7aVoI7VH2PfTWHPazPNeHS5fzyCTtX+2h58GLyxhUbMWKYEYbWrd9QJx7PrpzbmX2RPu6QijwJGvFSXRIjbIPZp5qLOC52IFLGVdt4OFkenblYkIrt/rMGa22fGCMK2bdz6XvTNSqnehHPft/6WoZI9fQvy2XZvgQZ3amtXD24p/SODiufuDt+0u+1DtJ/Fq+vsQsnm3hxdLCnCk8wDaCikQAJuEAA4gvEoRkzZphaQtVNICASBGlqETGMNG8LFiwwqdoQ4YmoHEQq2xsmFOyjoK1tGFcgjRrMiuixhCGkr9u3b59xiKnOYQbCA4QbnBfjIystr9V+bZ7tJz8wBnPVEN16hQtjGlfbb6j9wQVOLJGR4ZKt0VRw2EDB0sqWow4xiJpy0x98pAuMiHAcDVr5uFN6jUkrDzfjnILaPh7e1f/xz9Io6P1aByg01kcG/S7KiEP25z5WioghrSOTf9yjxX6jLiOiqNBB2l18VEo1Grsor1Q8/d3Ep1X52KnS4U6/RFS3n0YcIYVc7hFEZZ9eYcjqqLsKgcHtOkrvy26WdkPHyvbvPpKsxP2StHWdxI67wKgjnr7+4hUQbMSWouxMjb4q/05bbdT1s6ePr/i1CpO0PVslN6V8/OzMOTw1K4B/WKS+h5o6MOWoefbQtlwxfA/cdIyB9HhlpY6juGzt6b6nm43t3FwggUZGoLGIQsCyXu8rC4470sEp1F4UsrBhLDJ+3JkyfcZPZhVqBvft20d/18r/FiOSaKHW0oOFh4fLmWPHmGXrn71742x1hrt27SL9+5WnlJ/z6zzjeAInCliOCkTffT/dOkzOP29ytRG5tp10Yf36jYI6grDBgwdJrGY3sczahjHWBeefq+mAswX30ojQzszMMo4x/fr10d/w6r28t2hE1S6tu5ys0dv6V8848vTo3tUwsM7DZxIgARIggaZNwBVxqLGKQngHmo5C4OLnBSIQUr6hNpDl4YpJFUsUwQRKbQwTKK4IQ9gXhtoB7dq1c3hKDPRgtRGrcJy3eiI/+qeHsNjsrEuXTuaaDh1KMDUaHKWuQV7j7Tt2mv26dC7fv75AxM/TFGY6XzLozkjN9X7MTDaE9fCTCPWW9asmNQrSz8HSNQd/bc1dhSeYlVLO2XbOGnuGfP7FV/LttB/kxuuvdRgJ52xb9bEfhI6V+xNlb0r1nqfWNuxrWeew8kmTg+nZkq1ea4j4cdbeXb5Ja+iUynOXjpUB7SKqHJZTWGza9Gng+jrrDh01otDAdpHyzHmjHXqaIbVcfRhEM5iVUq4+zsE2SaA5EsDgEKIOBBGILaglaEXMQCSCUPPll1/KV199ZaJ0sJ/92ASepdaYwBk+9seiLau2HMY4qCOEWj0rV66UMWPGOPwbgnNhH0Tloq81CUPWtaFfRUVF5voqRw1hffbxyN3yWjkVo5ScuabmvE9rrQXRV4XD+b8tlMWLl8rEs8dXqY10UEWhBQsWav3EUOnVq2cFz2QvRJ/6+Uu6iodI1VNXBucTL42uQZrcwowS8Y+sGjWPc0H0QXq2nKNF0rZjoNbOqeoMk3O0WBJWV//bVJBeYsSayhE8qPGTrE43XiroBGk0tmoCp2ReWqsosK2mhtVIbbTbYXyI1i9y0bvG1R7ge5+k49XD+6XN4NEVj9YL8g+LkojegyRr7g9SqAKQZYjeCdTonfy0JEnatl7ajxhnbaqXZ0+/AH2P25o+pO7eoucbbyKITnYyRBpBGMIjO2G/HFq9UDqeMdnxYcrC0ZtoopX0/M5GHZ1uNo4vhmtJoGEJNCZRCCQOqUBi2UCtt1edtdd0rBHq7JGsKc1LdAwEBxSIPLAiTWWZqDX0YPYOJWaF/pOrUczWdvvI2bi4fZrh4YTjAdq19sOxGGs5Y5lZmbbj8vVc9mZtw8/QHhWokNoWUVKWIQpq6bIV5mVlcQjjL4hXcPCwtyPqDITH4YREmXzORPtNXCYBEiCBFkvApB/Xe+aQ4GCHvwUAg99AZLbAXLCj3wvsg3tQ1KdFO6fbnBGHGrMoBF7NUhhC8WWrlg9qEyGFytChQwV1hq6++mqZOXNmrT8r+ODCnM2Ra4lS+BDb1yKo3IFevXoZL+PK61v660Ga+i8oMEi9a7Ply6+/lTv/79YqSBboBMsh9byFd+0ZZ1S8EQ/RgpSWxcfv10mvTtbLWj3vX5CledU9pNPkUD2fc0206qrelDoXkbAyRwpuKBUrgsi5o8v38gkp/6oil78rNmjQAA3bHyErVqyUX+fON15Urhxf3/v2jiqfNFwSd1huGt5H06RVnGTK0Lo/2Aaz9sVya39f6RoeKntUUPp45Va598xBWF3F4Ktu/zblaTq2nUnp4qeTa/0diEJoYNrGXVXaaYgV6w8lmdMO7xjt8AcQgticnfvrpWvW+1CbiKx66RAbJYEmQgDijDUpAdHGSjcCkQh1Dh944AEjxHTr1s3UKkQ0MdLE/fTTT2ZbXV0mBtCWQOPn5yedOnWyOcY4OgeinCpHKVXeD0KPtQ8G34hIqiwMWeeFEw6iXTBQbmmGa7dS8pVqPbgirRFnWQdNHXfBBefKGvU8Xrtuvbz99rtyx//dpsKcvxETt23fIe++/5HJVT1Kf7svu+Qi61DzjNpDrcNaSbzWpIInMtL14X05VfMO8pDgGG/JOlgsR9bnSefJx6PQNLInfXeBigbuEtbLT1OAuR2PJnKT4jy9Nq0hhGMtK9L9E1bkyGF9VGdIRYdUc1ED/CVSHzAIUkmalvfAQv1MqTMN0vaeqiG9b1h3P4keHCCp2/Nl9/Q06XlFmEn7a7Wdn6IilW4LbOslrbo45pienmEm+dasXSfnTDpbi7xPsA6v8pyXelT2/PqdZMTv1qgYD4nuP7yCOIL6PKm7t2lNoxAJbn9iPApRKEbFmU1fvikHFv8iQdHtNRKrq6191NrJPLhXayUdMCIOopBOxTy8VcDu1ENFqsHqtLRd4uZNl66Tr9D39sT3tSA9RdLitkuAprsL6XCiL0HtYqWjRjptn/ah7Js/w0T0tB1yhm2cUpiVbuoq5acmSa9LbxKPSp9Pr4AgTcMcI0XZGRqxtE2KxmaLt3+QRhCVqqAWr4LTAWk3/Cwzpsc11oYNbr6//maaThDky9VTr5Su3bpq3bdd8sWX3+jfsFC59uqp6r3fWjZs3CzTvvtBkJ3g6qunNMhkwqm8jzy2ZRDA7+qMmZpqVkWUcP3cNlRNIXvaGRknhG2rzq/9dvvlNm3aGGEI6zLUEeVUDQ4T+O2DYAPz9vaSbpoy1zJPFbDrynAfOXvOXDOW69ghxvxW796zVycpy+eDNm7cpCnz+tn+/uG8EIwsUQh1CntrgXJ3nTSAE2v8/gOm3120DUsgq6u+sh0SIAESaEoEStRRG5GY+DtapL9zQRpNOmLEcOnRvZvtMnAPtUbTlqKmHZwAQkNDZJSmG++s97WW4fdg5arV6nC50zgN4HcSUayO0nVbx9THc03iUGMXhcDj1O4s6oPoKbb5wgsvGFEIqVVQ+HncuHGn2GLFw+HtC0MhaWcMgg9swIABpn6AM8e0xH2Cg4PMZeMmzt7w5b/77jvkX8+/KB998pm0b9fWhHRb+6zfsFGe+1d5TaPLL7tYkNPY3trqYNSyJUuXn7IwhEkR1Ala/OQhaTMsQHPge5gJEwg3vq09NGrISydOrDOWPwdEeUnXC1vJnh/TZe3rR2T4g23MMfZ7GcEpRCcRdALDkYX39jOrE9eouKTeuqhHBCvRiZkd36VpmjpNMaZpYBwZosmuuuZGef2Nt2X4sKGNqtbQAI2GGdGxjYkaenbOCvnnRWNNijdzbeoN9o9fVwrq52Af7GtvD4wfKvd8O09+2LJXglVQumFor4oDcxWUPl2zXf59yZm2iCIf5PLXydoCHdCn5uZLeEA5V6vdDYeT5Jv1jUMYCvApv7E5nFF1gg39f/m3tZJj571mXUNdPPdvWx5JtTw+0XAKO84pT3+cv1y3U25WEc9Dvwuw93QSc8XKVfJ/t98iI4YPMz/sz//7ZeMV+MD9vzffOUysPf/Cy6aQ+iMP/1G9B8ProptsgwQaHQF4MyFFHAQhjEOQHgUOJYgavv/++wUizTPPPGNEIMvBBBE71Xk/1fYCEQHUtm1b048+ffrI+++/b1Lh1rY9HAehC4IH2sYgPEXTo1SOhMb1I1LJPrroVM7ZFI+FUNNGi3DDIQgTSB9+/Jlcd81Usw4M++r7ceP118i7730o33z7nfy2aIl07dLJeLnt2LHLpMXp2bO7XKvHtG7dqgIC1IUaMWyYRoGtlnnzF5iJpt69ehjBDiJSbZ1ffFt5qvDjr6JQiqkdlLwpV0UCd0nbVaBjnhKJnRRihCF4WmBMEzXQX1K25svq145IhzODVRxwl6xDGqWmolL24SJp3c3XiDEVOo8XejzGSh4aBb3yxURt01cCwr3MsSnbC8yYqv2YIAnRNHX2lpNQJIjYLkgv9wSHoAPLjC+UtW8cMd8fCEHhffwl5ozy8SS2B7Xzki7nhkiW7rfr+3Q5ui5PEKnkq2nq4GhzZF2u5GmkEq4vtLOvtoOjKlqcjvs//fwL/V4nmSg5ONygrpUjO6Y3rWWImlNxY8PHr6j401lCVeDx1ro8eZqyLXX3VuWZptFEZ0h4jxNpBN21dk/r7n1NTaL4hT/Lqrf+pqLNEAk5Lh5BvEnevt4IKsEqzIR2LL9pLinIk5Sdm+XoxpWmO8X5ecoyTopys+Tgsl8lTaOBYP7hUVoDc7yJ9DEr9B8ILrFnniebNfJnr4pZyZraLkprDXkHtzJ9Tdq2VnmnSszoSSoadrH9jfLSaJ/o/iMkJ/GgnmOubPrsdYnTCCj0C6nhMvbtVKZJEta1t6apS6wggOHc7iqcBoRH67V0UB5bZPUbf9NjO0lOsqZo2r9Xo8ViJKhtRwmJ6Wy66iobn8j2AhHvm2+nmb9Vfhphd8tN18nceQvkl9lzNJVjhERHRWraqjPk51m/mO9RB53w7dixo5w7eaI5J/8hgcZCoDGKQqi1XKh/52AY6/jruKYmCwo6UX/XXlCq6Ziatp115hkmatYShnD+cWeNremQU9oWqI4b52l6usiI8nujkSNGyCeffa5ju1LJydXfvOwc/b0v/91J1NT2O1QAgiFS6uKLNF3occPv80c6HkAk1Bp1DKEwZJHhMwmQQEsksHzFCtm0eavOKbkbp4eU1FQzVvPScaJ1P4NoTdxLeWomHzj0IAXpL7/8KpfqPY/llADnd4jxyKKFoIAU3WfmT7NkypTLqx2v1xdvR+LQAE0bjns2OI6OGT2qxhSk9dUvZ9ptdsIQ0rPA/v73v8u4OhaFEJVipXtzVhjChAxs9erVZnLISmVnVvIfGwGEYf88a7Ys0tQqH370qdxy8w22bZdfeokqxevMF+qpZ/4un372pWDSZL963cD7Bl+y3irA3Xv3XbZjrAXcAI5U5RkT16+/8ZbM1S9lzx7djafPk48/au3m9HPvq8Nk4aY8ST7+qHygf6Sn9LsxQtrbTUxgHwg3yeoNe2Rtrsy5J146nBVkvGJRZBliT/LmfGnd01eiBgU4nJjwUdEovK+fqW204M8Hpc1wnZjTdCw4Dh66EX21QLVO1DiytjpBdfddt8tLr7wujzz6mLzz5mv1klLu09XbxIowgYeVZS+pgOFxfLalTXCA3DCst7XJiAtPTB4pf/zhN0GEzBUfzJAxWk8Huy+NSzAp3XpEthbsYwkR1sE9IlvJ78YMkHeWbpSPV22VOTvipU+bcAlSz7GtR1Nl1/HIoN36PDimvOg72ji/dyf5YfMeeWT6IpkysLtEaZ+OaK2izYkp8qu2MaF7B5m364B1GttzXGqmfLvhhGiEaCXYyv1HBJFNll2pbVqp7qx1tXme0K2DfK0izM/b4sRbfwyH6jWUaTTCPu3HbO2nLkovjbjafjStNs3XeAwihpBmD/WY/jBtgYzS9yQtL182HEqWTM0pPlC3DdH+wPvhnXffN2298tqb8uVnH8qiRUtk+o8zzLqPP/1cnn7yMePpOHfefLMON0KOIv9q7BA3kkATIZCenq6TAjvMhCREEwg+qTrQRR0hRA2df/758qc//ck2yVpfl4XzQphCH6w+oUbiqVqYToj37dvXiF8Y18Dpxd4gCq1Zs8bk0rfGP/bbW8oynFJQLwGexquVx/Chg40whOvHTcvUKy83Nz3//e/7sk093BISEgwaRGBhIunWW24Qe8cWixsmn86eMM6k8Zn+40wzJsW4tFNsrKkjaN1IWfs7+4xI6A5nBZsIoPhfM+XQ0hzjbAJhpaeOezraRfAExfhI3+vDZds3qZK4SmtTrc41p0GtxVZdfKTPDeEmHR2idCqbT6incappOyxQtn2dqpHUuSY9HRxqQjr5SM8pYSpgnJhEtI5HOrjDK3IlR0Une8tXUefAgmyzCqnwPHzcKwhDcJiJGhggY55oJ7t+0EiWpdmS/lV5Wl+cE6nm+lwXLrFnhzgce6FhpKQAV0xoRkdHSSutm1GdBajY0ueqOyS810A5sHSOpGvETdqecnEGgoh/eBvpdu4UiR1/YYXoHLTn1ypcelx0nbSK7S57Zn8rh1cukEMr5plTQYyJ7DdMOk+8RAWT8jRM2FCqHpYQgg4e38/sfPyftL3bBA9YqLYZ2XeIiKaAs8zN3UMitJ8j7nlChaHvJWHdUsn4abfZ7KY36QERbU1/YkZNrPL3CkJT78tv0fe7l+ydPU0jzeIk88Ae3c9dI8hCpdP4i6TLpEs1wr6isGmdG2JZ78tv1XpLH5roqvT4XWKusc8Qc6wlCln7u8IG4ySk70R9E0wYd+4ca6IX4e3vhUkDfT87dYo10Y6dVAwyIn5wSIX6ItZ5+UwCDUkA97dWpBD6AaeDRYuX1LpLmIMYpr9FlSN9XW0w6AuoEwAAQABJREFUV8UQy3x9K4r41nr7Z/uoVogiTc2GqVOlJQqh735+vjrZGG7SwuE1xnaWMLQ3bp/o7ZmxyinmsBIe7Ds0ejEtLd14tmNClEYCJEACLY0AnCa3bdthxBzc90Spww6igpBmG/XoMO7O07mnPSoKYSx3ycUXqLNca+P4s3LVGhWUthhhCM7HEIWC1QHhEhWLgjRbxdJly7WNLbJZRSfUuTvdVlkcQmpRTcbdqEUhMGpywhAmJWCYeKlsyK2/ceNGsxqpWSobtmOCpLb273//2+TvRx+cFZ2QJgaTM/Csfemll+SRRx6p7emb9XFImYJUe3v27DHCj70w5K6T+f/8xzNmcIyol71xceYBIPBevu7aq+S2WzRdhXotObLH/vIn+dOjj8vWbds0xHCHeSDFzV8ffbjG1DqV28pJKJbl/0yQdmMCpfslrdVbskgnUbQehBZKLszQFBgHCo1Qs+qVRPU89TETDlYbnn7ucvYLHWSneqzu1sghTFBY5uHrJl0uCDUTLcf1E2tThedRf25rzo/Jlr0zVZTQsWSoTqQMuTuqWlHIauCqqVN0gmqe/oHcIo8/+TeBKAbP9bq0FRpdsiOpqkgx1y7dWU8VeeyFIZzf18tTnrtwrPx32SYjyED0gCGyZ3LPWLljdH+zj1lZ6Z8rBnSTfioGvbZonRGCErP2mz1w7OD2UfLA+CECMcrefnfGAPHSwsfTN++VFxassW0K1DpF94wdJBCcHAlDKTn5RpCxHXB8IT4tU/CwbFzXmDoRhtDvf1x4hry+aL18v2m3eVjngCjzx3FD5BUV3erLnta6Rk/+vEy2qGCG8yMNQhdN3wemOD8sWkVHeLAjQmLQwP5mHaL28IOIHNvWTVE/LTaL7yfErP66TCOB5kgAn/lNmzbJ/PnzdUDbWUaPHm0uExFBcfq7he8FBrWVo4OstGx1zQSToyPUs3XGjBny9ddfy8SJE20pzmp7rg4dOsikSZPMeGb58uVyyy232H57MYkFkQLC0ODBg81+tT1PQx93xphRsmLpb7XuBiZ//vroI3L7rTdJEmo32aU8QKOIFhut456RGmWZofWCDh1OEB9d11GLX9tPojnqADzkHnnoj9r2zZpG97D5POF88KQ7FfOP0BSrN0dIn2vDNc1XqUm55h1YdVyFcQoiekZq9HNhdpnkJRVrzcUyFT00EkSjiRBZDet+acX+IJpn/L862Lo4Qo9HBBAidnzV+QWONdaxtp2OL4T38pPJb8RWXu3ca+1OUDtvGXJPlKkPCTEJkd9wuPHT6Gt3r/L+VtcYnBn+8ezT5v6hjX6nqhtrWsd7a6q0mFFnm5RvxXka5a1RN8eOgU+URotXFb2s4/Ds6esnbYedqVFNY6UkP1eQjg2/m36tIzTq6EQklHWMT1CIdL/gGvOw1rn0rG8mxKz+198rfa++S5DqrlBTvKFd39Awje6pPiWTp0bixIycUH6dOVkmSgi1iwIiovR9rPq5se+Xm6bCi+g9UAW0V/Q686S0MN+ISEi/V505ywZjlZEjhsn/vlLPfJ2wbaeZBmAoSD9Gv9elOhkRGVkukCF93LnnnWPuBVprbTgaCTQmAohEOXK8Bg/6hd+JUzX8fvTsUfVviSvt2kcAIXoIKXQrj2vs28P30DJM2jU1c/QLYS+IoVaSZalpJ+6DD6vDR6LWVLI3jAdhYJat6XghYNNIgARIoKURQOYJpIZrExFuRCFcP+aQFi9ZamoJ4TWyUEBoh6M/7p9hvXr21LRxa0x2Bby2fl/gCBmsznUwtANhCMc3lOGeH5FC5aKQ3odo3ypntmqovlV33iYnDMXGxpprWbx4sclvj1QdliFFByYtMAHzwQcfqGp4ibXJpDW6/vrrZenSpbZ11S1ceeWVmvf9bbH3dv3Pf/4jb7zxhjnkiSeeMGKP/fHWh9X6cFrbIjTsGPWO7r77bnnqqaeMF+1tt91mbTbPBw4ckPXr11fob4UdmsALCDn2Yo6rXYYXJqINoBRjkszRIPOiC88XPDBI3rcv3qjEMTExmt7G0ZDtRA+gQH/4/tta7DFB4uMPqLdgkKbU6eaSKITWNn2cZCZLhtwTbVLBte5eNR/9ure0b7MzNSUHctZ7n+iELsFzteeVrc0DkxJIt+IdqF6ZUd4Vct5XOMjuBSZozno2Rj1Ej0l+crH4R3iddELDOhyMXnnpeXnm2edM5NXOXbvl2WeekD6a97iu7I0pZ9e6KUSoPHL2MLl/3GCN3in/Ix4d7C/eNUwSWCfrrkLOf648W4r0xyUupTwlU5fwEFtKOms/6xmi0V0aaXSjpkNLyMyRdE1VF6IebxA9rKikefdMsXa3PaPWj6P1th3qYaGvil5vXzVJjmarV7ZGNWGCqGPrYFNjCaf718XVe0H8ddJIeXTiMeVQ8/ejum4HqVD20mXjDNek7DyJCgowgpr9/gjZ/e5/X8i++P22fLCYhJk183vBzVGseuPCkHpnxvRvTdQkJtZoJNCUCSB6+N133zWTi5dddpmZKMYA95dffpEnn3zSvB4+fLhceuml5jLhwICJSNTk2bBhgxmjQDiCQUh55ZVX5M033zSv6/If1DG6+eabZeHChTJnzhxT0+jRRx816e1wHvzOHtYC0jt37pRhmp7MGk9BtLA8iuHRhWuzDNcxZswYMx6aN2+eGSvdc889ZjOuDel8MXHev39/jeztaR3WIp/hnY0bFTyqM4xbMX60xpDV7Vd5PRhH6M0UHnVtHt5umhb35LcHGNOgZmJt6iaizzjeT8UkPE6XuWtUE8QrPFyxQE2fiIcrhqgbpJDDw1XDJCtEpJMJSa62W9P+5RFNUUbAqmm/ytvQV28VkvBw1cqvU8cW/s6zdYYNvldIRY2HZfg+hh2fWLDW4W+dfSSAtZ7PJNAYCODzi8hTZNPA5NiAfn1NtFtt+4bvAO6JT9Uw4eWn0Uso8l1WdkzTqeUaL+3q2oXAZZn9d9Ja1xSf8XfIkWWo97pliAauyay61TXtw20kQAIk0BwJIC05hBykaV7w2yKtE9fFRANhrgt16WAQziHCIyJoydJl0lHn+VFLCIaU3TDcB3lqZh2km/P317qi6rC8otI+ZsfT/A+y6iB9HCKFcE+N38EZM2fpXPZ5xlH0NHfHqdOdvrsxp7pz8p0gqiBXPjzUcbN9+eWXazhumonEGTt2rPFeffzxx+XHH3/UYn+9Zfz48WbSY9myZSaVwHnnnSezZs2q9kS4Od+9e7cMHDjQeLxi8gber4hkgZ1zzjnyu9/9rsrxZ5xxhnz66afmvP/4xz/kL3/5i22fO++8U8PiFsuXX34pt99+u7z66qsydOhQ47m7TaNYlixZYtIb7N2716Q/sB3YAhcQWXAyg4iEhyuGAVx7/bzgUVvL3FdkhJiavEuRpx+GfPs1GTxVfUJqF7Hjod6tlUWnms5lbcNg/KUX/inf/O87efX1N+Se3/9R5s356aTer9bxp+MZQlCHVrXzZMOxPTW1mrPmr5FKXVUMauyGW49oFWXwcMUgcnmgoMMpGri2D63+PcHEin2RQJwOP4DWxLJ1etYVskjwuTkQQNQynE0wRujSpYtJBbZ1qxbGVCEFwskf/vAH28DPElM+/PBD45wyYcIEgXAEJ4iVK1ea2j14vUJzLdelQTwYOXKk/PnPf5ann35aXnvtNfnuu+/MuRH5DCcaiDkYnGPMYjmtQMhCvY1W6kGPCCg4tzzwwANmHX5L0dfHHnvMPHDct99+a4Qv9P+IesdOnjxZHn744bq8FLZFAiRAAiRAAi2OQN/j98UQhzZv3SZt27WpEoHaEFCQDQXCEAwTYNU5GsIBBc40loXqcc3ZArQeEYQy2PBhQ0wkcHXXC4Y0EiABEmiJBHA/OWb0SJk1+1dNqb3DPMDBW0tCDBuqqY/VIPggqxSEI0QA4QFDarnBgwaaZTgqDB0yWMuGrJa16zaYdfgHabtR07UhDL+JP/8y21ZTCJFCEIWOarr1xiwONTlhaNSoUWZy48EHH9QQshz55JNPzPsNUQgPeMMma9oORPds377dPDBBOWXKFHnxxRcFHq41CUNIuwKB57777jOpV1atWmXax8TJQw89JIgWgidaZTv33HONhywmUZByzl4Ywv5ffPGF8R5GKrnNmzebh9UGUq5gf6RkojVeAogQOrQkW9ZrVFA/TbeCnPz2lrwlz0QLefq7a82f2ok+9u3V1/JULcQ2ePBALcL7a6MSherretkuCZAACdQlAYgmN910k9aqK5a33nrLjDPQPm7yEXGMqCFEs1oG8RRiEASWZ599Vuvj7TcPpDRFejeIKEhDd+ONN1qH1NkznF0Q0QNnl6eeekqQ/u2bb74x7cN7GFFFd911ly26yTpxbGysiWLG2AXjprPPPtsIQ9iONpFCDulI4Qjz22+/mcNw/ddcc405D46nkQAJkAAJkAAJnBoBe3EIacEnn3N2g4tDsbEdbDV2Nm7cXK0whAwfmVnZBgAm/JDKzjJPTSlpWVFRsbXo1LO92xuilhqLYXx0NCnZdKc8erpfY+ka+0ECJEACjYoA6ghdPfVK2aPBEcnJKRoRpOUYdB3m3S2DqBKuqbJRvw212WJjO+rvX6yp9WbtA5EIvy1798RJVna2aaOT7offnNNtlUUhq6wCIoUauzjkpp4cTv+aQnDJNrDLU6CcbtD250M/ILAgNQsKKltpWax9EFG0ZcsWE4KGCRH8OLtqSAsHYQg/8ii27Ewb8JhF2hXUFqouzBgc4aULwQpRTVb6Flf7x/1PLwHkpV/67GHJ3Fdo6vsEx3hrChVPKS06JrlHi6UgrURQ/HjYfdHSZnjTy6F8emnybCRAAiTgPAFEt6SUucmG4vLpgI6B/jI55tRTojjfAzHjAYgsqBmIKBxEA8NBBRHFiP5B6rSafs8x3EI9mV27dpl6hfj9P50eo9b5ERUNIQqiEBxSqhurQPhCqlukm0NqXavGoz0ztAOhC/Ubcf2n83rs+8FlEiABEiABEqgPAvtz8mT2wSTTNMYe/TxKzHLbtuX1q+rjnI7a3KLp1hE5hDSVDS0OITr68y++0uiY8vTfmLxDkW/78QTGSjNm/ix5+eWRRaj9NWTwINulof7Dhx9/Zl57aRaHW266UTOolItFOTm5Mv3HGZKRmWW2D+zfz9QIsw4uLCyS9z74yLzEqPDWW26sUp8vT+sfffjRp2YfpAK96cbrzLL1z28LF2n94R3m5QTte69eJ9Lf1rQNB/w8a7ZJo43lCy8416Q4wvKOnbs0fdBvWNTJTX+54fprNU15VYdiswP/IQESIAESaDYEqhOFrAvEPbMlDkVpavbGllbuhKuG1eMm8gxRxSrs7KjLmOw41QgcqJVIReeKIW3LyQx1h1C8mda0CCDn/tkvdJCkzfmSuDpH8rTOD2oFIR9/1CB/Ce7gI50mhZj6Q03rythbEiABEiABZwlAYCnVmmYwRP3A+cQZw4QJ0rMhMrkhzDo/UsE5Y15aQwxp8vCozhDCD+ccGgmQAAmQAAmQQP0RaEyRQ4g4Hj58mMxfsNBc8PYdOyU1VWuLaiQRRJhErQe8d2+cFKmDCSwoMEAGqLhjb4g6tmoVFReXyPfTf5R+fXqrkJSvgs12U5PBfn/7ZR+tg+rv52tEJ3g4L122XAYNHKCR3CWmUDnGO0g3hAfay1UR6og6DaP+RH0aUmtv3brdnCtXRbOZP82SEcOGqmNNa3UcLpTExETZruLR+edOtolg9dkftk0CJEACJFD/BE4mCqEHuGduzJFDTVYYqv+3l2cggaoEUCw5aqC/eVTdyjUkQAIkQAIkQAIkQAIkQAIkQAIkULcEGpM4hCihUo0cWrJshXGWSdKMKHhUtqjICDln0kStF1Fx2gniTZ8+vbTg+HpzCFIJzddaEjB3rZM6auRwWb6iPKW/WVnpn379+moh8jVm7Y6duzVaZ7dZRmoiCDEwpBfaowIVHHqmfTddbrz+mio1UM2OdfQPrmnC+LPkxxk/mVpDhw4d1hpLh6u0vlFLD6AuBo0ESIAESKBpE3BGFLKusDGLQ4xttd4lPpMACZAACZAACZAACZAACZAACZAACZBAIyQAcejMsWPkWFmZoOZQ3L59DdZL9OXKyy+R9u3aVomACQkOliFa++Hyyy7RNLtBDvuI1HI9unWtsK1VaKicf965pri4t0YuV2eDtO1+fXtX2YwUdpb11QgkKz0d1qVqjYr6tlatQuWaq6eYuktelcQwRDoNHjRAt1Xtd333i+2TAAmQAAnULQFXRCHrzJY4hHRyR5OSTHo5pJlraGuyNYYaGhzPTwIkQAIkQAIkcHoINKYaQ6gT+Mgjj8jdd999ei6eZyEBEiABEiABEmgQAo2lxlDli7evOXTu5EmCYtsNaaaOYWamoHZQeFiYSZvjbH+Q7i1dRZtQFYVQm8cVQ72htLQ0KTtWJiHBIZri90ThcrRToBNuqamp4u3lrXWjWzlVM9qV89e0L5hkKhPUTEINxqAg1iCuiRe3kQAJkEBTIXBEU6b+oLXwkF5+zOhRMnBAf5e6bl9zKDoq0jhRIOq0oaxiTG9D9YLnJQESIAESIAESIIFGTCBYvV+HDRsmGRkZ0r59+0bcU3aNBEiABEiABEigOROwTyu3cuWqBheGMKGFaB88XDVTD6idn6uHmf0RhdOmTXS1x/pqXYd2bdtWu70+N4AJxC48aCRAAiRAAs2HAKJ9ykrLaiUKgYIVOTRj5s+SkpIqRUVFLjlU1DVJCkN1TZTtkQAJkAAJkAAJNDsCPXv2lDfffLPZXRcviARIgARIgARIoOkRgDgUGBQknh4eTa/z7DEJkAAJkAAJNFEC/bXOXdcunTXKtWKUqiuXA3EI6VYLCgoaVBRCnykMufLOcV8SIAESIAESIAESIAESIAESIAESIAESaGACsR07NHAPeHoSIAESIAESaFkEEBF6KqKQRcvd3V38/V1LoWodW5fP7nXZGNsiARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARJovAQoDDXe94Y9IwESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIIE6JUBhqE5xsjESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESaLwEKAw13veGPSMBEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiCBOiVAYahOcbIxEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEmi8BCgMNd73hj0jARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIggTolQGGoTnGyMRIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARJovAQoDDXe94Y9IwESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIIE6JUBhqE5xsjESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESaLwEPBtv19gzEiABEiABEiABEjhBINz9mAz0OiZSnCNxcTknNnCJBEiABEiABEiABOqBwEAvN9lQ7FYPLbNJEiABEiABEiABEmhYArUShuLi4hq21zw7CZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZCAywRqJQy5fBYeQAIkQAIkQAIkQAKnSCClzE3mFrpJx0B/mRwTeYqt8XASIAESIAESIAESqJ7A/pw82XAwqfoduIUESIAESIAESIAEmjCBWglDnTt3bsKXzK6TAAmQAAmQAAk0JQKMVG5K7xb7SgIkQAIkQAIkQAIkQAIkQAIkQAIk0NgJuDf2DrJ/JEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACdUOAwlDdcGQrJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJNDoCVAYavRvETtIAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAnVDgMJQ3XBkKyRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiTQ6AlQGGr0bxE7SAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAJ1Q4DCUN1wZCskQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIk0OgJUBhq9G8RO0gCJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACdUOAwlDdcGQrJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJNDoCVAYavRvETtIAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAnVDgMJQ3XBkKyRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiTQ6Al4NvoesoMkQAIkQAIkQAIk0EwIlJaWSkpKiqxdu17mzp0r/v7+8uCDf5RWrVqZKywsLJStW7fJ9z9Mly1btkhOTo4MGjRQrpo6RQYMGCCenhy6NZOPAi+DBEiABEiABEiABEiABEiABEiABBqMQIufXTh27JgUl5RImU7U+Pj4iJubm8M3o6ysTEp0P5i3t7fDfbiSBEiABEiABEiABGoikJubKytWrpJv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AZg7Z9bgHfQgGllwcGsOqMLCImRkZEouzqo+HZ32p/1q/2pBQUF92j87IwESIAESaEuAHkNtefAVCZAACZAACZAACRgCgQEBWH7DjTh65Bj+z//8D15fuw6JiWPgMGoU0tIvIj09HeMkhvyKW26Gu4R5oZEACZAACZAACZAACQxeArm5eWi5PLxRcj+XOCYBixYugKOj4+Ad9CAamYYcDA0NRZ54XZWXl5tHfw0vNDSkxyEO+2ss7JcESIAEhisBCkPD9cryvEiABEiABEiABK6JgE4YjBfh579++J9Yu+4NvPHGG9i2fbvpU8PG3Xbbajzx+GNISkq6puOwMQmQAAmQAAmQAAkMZwKLFi3EgvnzzSk6Og5c4Jro6Cjcv+YeaLhgf3+/fs2TM1yvZ1BQINzd3SScXImEWq5BfX1Dn52qi4sz3NzcobmiPD09+6xfdkQCJEACJGCfAIUh+1xYSgIkQAIkQAIkQAKGgK5Y/PKXnsEXPv8USkpKoHmF/P39oYmXVTyikQAJkAAJkAAJkAAJdE7A0cEB+hhoc5AxBAUGDvQwhvzxVbShcDPkLyNPgARIgARAYYhvAhIgARIgARIgARK4CgEVgDQpcVhY2FVqcjcJkAAJkAAJkAAJkAAJkAAJkAAJkAAJDG4CA79kY3Dz4ehIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIYNgQoDA0bC4lT4QESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAEuiZAYahrPtxLAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAsOGAIWhYXMpeSIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIk0DUBCkNd8+FeEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEhg2BCgMDZtLyRMhARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgga4JOHW9m3ubm5uxb/9+bNr0ESoqKjFmzGg8+MD98PX1JRwSIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESGFIE6DF0lcv17ob38M1vfgtZmVnw8fHGu+9uwP/89OdXacXdJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJDD4CNBjqItrUlZWju/927/j0YcfFnHoOTg4OKCktBROTsTWBTbuIgESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESGKQEqHB0cWH+8corcHJ0wuOPf86IQlrV38+vixbcRQIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAKDlwCFoS6uzeHDhzFp4kQ4Ojpg9+49qG+oR1xsHKJjouEwalQXLbmLBEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABAYfAQpDXVyTjIwshIQE49e//i0qKiuQmZkNV1cXfO2rX8GMGdPbtNy9ezcqKyvblFleNDU1mc2amhoUFhZaivncTQIWfsq3vr6+m61YrT2BhoYGU8T3YHsy3Xvd3NxMft1D1Wkty99yVVXVgPwt19XVdTo27iABEiABEiABEiABEiABEiABEiABEiABEiCBkUKAwlAnV7qlpcWIQaWlJbj33nswcUKyCD9V+I/v/wA/+e//g3VrX2vT8vTp0ygoKGhT1v6Fihrl5eXti/m6mwRqa2uhD9q1EeB7kPyujcC1t1aRXB8DYZorjkYCJEACJEACJEACJEACJEACJEACJEACJEACI5kAhaFOrv4oCRUXEhyMuLg4rFp5q4STczQ1ly1dip//8pcyqVkLd3c3a+uHH34YKibZM12l/uKLL8LHx8f0Z68OyzonoGJQbm4uAgICDMPOa3JPVwSUobLU9zSt5wQyMjKgf+Ex0dE9b8wWhoDlbzkwMBDe3t7XnYqK9wMlSF33k+UBSYAESIAESIAESIAESIAESIAESIAESIAESKATAhSGOgGjxYljEnHy1Mk2gk+wiEWODo5obNSwXFeEIVdX1y56at2lYhNXq18VU4cKFmb6bNnuUIkF3SZAht1G1aGiZhYjvw5Yul2gn4FqA/VZaDl+twfMiiRAAiRAAiRAAiRAAiRAAiRAAoaAJTw9cZAACZDA9STg7Ox8PQ83oo7FmDpdXO41a+5FRkYmPtq82YSAy8/Px85duzBlypQBWe3exVC5iwRIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgASuSoAeQ10gmj59Gp584nH88Y9/ws6du1FRUQ4Vh55//htdtOIuEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEhicBCgMdXFdNGTUF7/4eUyeMgkXL16Ck+QZmjhpIiZPmtRFK+4iARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIggcFJgMLQVa6Lu7s7li1diqamJlPTyYnIroKMu0mABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABAYpAaoc3bgwmrCcglA3QLEKCZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZDAoCbgMKhHx8GRAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAn0GQEKQ32Gkh2RAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQwOAmQGFocF8fjo4ESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAE+owAhaE+Q8mOSIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESGBwE6AwNLivD0dHAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAn1GgMJQn6FkRyRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiQwuAlQGBrc14ejIwESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIIE+I+DUZz2xIxIgARIgARIgARIggT4j0NzcjIqKShQUFMDNzRVhYWFwcuKtW58BZkckQAIkQAIkQAIkQAIkQAIkQAIkMEIJ0GNohF54njYJkAAJkAAJkMDgJlBRUYHNmzfjP77/A7y+dp2IRBWDe8AcHQmQAAmQAAmQAAmQAAmQAAmQAAmQwJAgwGWnQ+IycZAkQAIkQAIkQAIjiUBjYyNSU1Px4aaPkJmZhcTEMWhqahpJCHiuJEACJEACJEACJEACJDDgBGpr6/D+xg86HcecObMQFRnZYf/uPfvQ0tKCWTNnmH37DxzEqFGjMG/u7A51r1Zw7P+zdx/gcV3ngfdfYDAVvTcWgL03sVMkJVnNkizJjiXHJYos2/LGibNer+PYcZyN7c9Z50m8jr2bxLHTY8exZMmSVaguqrCKvYkNBEACRCF6H9TvvBecwQCYIQpnQGDwP88znDvnnnvuPb87AMl55z2noka8XT2yMj9THDa+4x/MazzeR48el/NFRcG6k/j4eLn7rjuC7qMSgWgRIDAULXeScSCAAAIIIIBA1AhU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JEACJEACJEACJEACJEACJEACJEACJEACJEAC3kaANYa87Yhyf0iABEiABEiABEiABEiABEiABEiABEjAwwj07t1LevboYXrt7884Zg87fOwuCZAACZCAhxGgY8jDDhi7SwIkQAIkQAIkQAIkQAIkQAIkQAIkQALeRsDfz0/wopEACZAACZAACbieAK+4rmfMFkiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEjALQjQMeQWh4GdIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAHXE6BjyPWM2QIJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJuAUBOobc4jCwEyRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiTgegIBrm+CLZAACZAACZAACZCA8wicSM+Qj/Yfd94GuSUSIAESIAESIAESIAESIAESIAESIAES8CECt+QYiouL8yFE3FUSIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAES8A4ClJLzjuPIvSABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiCBYglU+EGt2KW4AAmQAAmQAAmQAAmQAAmQAAmQAAmQAAn4KIH4+Hiz5+Hh4T5KgLvt6wSys7N9HQH3nwRI4DYQqFix4m1o1TeavCUpOd9Aw70kARIgARIgARIgARIgARIgARIgARIgARIgARLg4Cx/AyRAAiTgXQSYMeRdx5N7QwIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIOCbDGkEM0nEECJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEAC3kWAjiHvOp7cGxIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARJwSICOIYdoOIMESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAEvIsAHUPedTy5NyRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiRAAiTgkAAdQw7RcAYJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJkAAJeBcBOoa863hyb0iABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEjAIQE6hhyi4QwSIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAES8C4CdAx51/Hk3pAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZAACZCAQwJ0DDlEwxkkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIkQAIk4F0E6BjyruPJvSEBEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABEiABhwToGHKIhjNIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIwLsI0DHkXceTe0MCJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACJEACDgnQMeQQDWeQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQAAmQgHcRoGPIu44n94YESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAESIAEHBKgY8ghGs4gARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgARIgAe8i8P/kFu9JFZ12fgAAAABJRU5ErkJggg==" + } + }, + "cell_type": "markdown", + "id": "0cf240cf", + "metadata": {}, + "source": [ + "![image.png](attachment:image.png)" + ] + }, + { + "cell_type": "markdown", + "id": "4b9fc573", + "metadata": {}, + "source": [ + "To share your creation, you can click \"open in new tab\" in the right-hand drawer, and then click \"Publish\" to save it to wandb." + ] + }, + { + "cell_type": "markdown", + "id": "9a4092e3", + "metadata": {}, + "source": [ + "### Bonus: Weaving everything together" + ] + }, + { + "cell_type": "markdown", + "id": "4f211f9e", + "metadata": {}, + "source": [ + "Here's a **Weave Board** for all these panels, defined in code. We're actively working on additional examples and tutorials to explain these concepts in more detail. To inspire some initial exploration, you can:\n", + "* **open the panel in a new window** from the right-hand drawer on hover, to view a full-page Weave Board\n", + "* **see the details of selected data** by clicking the mouse button in the bottom right of the PanelPlot, then clicking and dragging to select a subregion of points\n", + "* **customize the Board** by adding, changing, and interconnecting panels\n", + "* **save and share your work** by renaming the Board and publishing it to W&B" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e194b3ce", + "metadata": {}, + "outputs": [], + "source": [ + "weave.legacy.weave.panels.Board(\n", + " vars={\n", + " 'data': data,\n", + " 'limit': 1005,\n", + " 'limited_data': lambda data, limit: data.limit(limit),\n", + " 'embeddings': lambda limited_data: openai.openai_embed(limited_data['name'], {\"model\": \"text-embedding-ada-002\"}),\n", + " 'clusterable_projection': lambda embeddings: umap.umap_project(\n", + " embeddings, {\n", + " 'n_neighbors': 30,\n", + " 'min_dist': 0,\n", + " 'n_components': 2,\n", + " }\n", + " ),\n", + " 'clusters': lambda clusterable_projection: hdbscan.hdbscan_cluster(clusterable_projection, {\n", + " 'min_samples': 10,\n", + " 'min_cluster_size': 50\n", + " }),\n", + " 'projection': lambda embeddings: umap.umap_project(embeddings, {})\n", + " },\n", + " panels=[\n", + " weave.legacy.weave.panels.BoardPanel(\n", + " lambda limited_data, projection, clusters: weave.legacy.weave.panels.Plot(\n", + " limited_data,\n", + " x=lambda row, index: projection[index][0],\n", + " y=lambda row, index: projection[index][1],\n", + " label=lambda row, index: clusters[index],\n", + " ),\n", + " id='projection_plot',\n", + " layout=weave.legacy.weave.panels.BoardPanelLayout(x=0, y=0, w=24, h=12)\n", + " ),\n", + " weave.legacy.weave.panels.BoardPanel(\n", + " lambda projection_plot: projection_plot.selected_data(),\n", + " layout=weave.legacy.weave.panels.BoardPanelLayout(x=0, y=0, w=24, h=12)\n", + " )\n", + " ]\n", + ")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/weave/legacy/examples/experimental/AutoBoard.ipynb b/weave/legacy/examples/experimental/AutoBoard.ipynb new file mode 100644 index 00000000000..264a035b27e --- /dev/null +++ b/weave/legacy/examples/experimental/AutoBoard.ipynb @@ -0,0 +1,91 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "315969ff", + "metadata": {}, + "outputs": [], + "source": [ + "import weave\n", + "from weave.legacy.scripts import syndata\n", + "from weave.legacy.scripts import syndata_mon\n", + "from weave.legacy.weave.panels_py import panel_autoboard" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a6b988de", + "metadata": {}, + "outputs": [], + "source": [ + "predictions = weave.save(syndata_mon.random_predictions(10), 'predictions')\n", + "#predictions" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "64aef598", + "metadata": {}, + "outputs": [], + "source": [ + "panel_autoboard.auto_panels(predictions)\n", + "#weave.legacy.weave.ops.project('shawn', 'fasion-sweep').runs().history().concat()['_step'].type" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "78a3a155", + "metadata": {}, + "outputs": [], + "source": [ + "metrics = weave.save(syndata.random_metrics(10000, 10), 'syndata')\n", + "#metrics" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "77e03fd6", + "metadata": {}, + "outputs": [], + "source": [ + "#panel_autoboard.auto_panels(metrics)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8b417fd2", + "metadata": {}, + "outputs": [], + "source": [ + "#panel_autoboard.auto_panels(weave.legacy.weave.ops.project('shawn', 'oai-api5').run('test4').history2())" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/experimental/Closures.ipynb b/weave/legacy/examples/experimental/Closures.ipynb similarity index 100% rename from examples/experimental/Closures.ipynb rename to weave/legacy/examples/experimental/Closures.ipynb diff --git a/weave/legacy/examples/experimental/Monitor.ipynb b/weave/legacy/examples/experimental/Monitor.ipynb new file mode 100644 index 00000000000..4f6ac6046aa --- /dev/null +++ b/weave/legacy/examples/experimental/Monitor.ipynb @@ -0,0 +1,219 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "f7512dfa", + "metadata": {}, + "source": [ + "# Model monitoring dashboard example\n", + "\n", + "This notebook currently requires the plotly library to be installed.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "039f298a", + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "import weave\n", + "# Weave package now defaults to eager mode, but lazy mode required for this example notebook for now.\n", + "weave.use_lazy_execution()\n", + "from weave.legacy.scripts import syndata_mon\n", + "from weave.legacy.weave.arrow.list_ import dataframe_to_arrow\n", + "from weave.legacy.weave.ecosystem import wandb" + ] + }, + { + "cell_type": "markdown", + "id": "8519889f", + "metadata": {}, + "source": [ + "## Create synthetic data\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "29f45c3d", + "metadata": {}, + "outputs": [], + "source": [ + "data = syndata_mon.random_predictions(10)\n", + "timestamps = data._arrow_data.field('timestamp')\n", + "start_date = timestamps[0].as_py().to_pydatetime()\n", + "end_date = timestamps[-1].as_py().to_pydatetime()\n", + "predictions = weave.save(data, 'predictions')" + ] + }, + { + "cell_type": "markdown", + "id": "cdd59e39", + "metadata": {}, + "source": [ + "## Create a Weave Board for the data\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8049b6dd", + "metadata": {}, + "outputs": [], + "source": [ + "# Create a dashboard for our data\n", + "board = weave.legacy.weave.panels.Board(\n", + " vars={\n", + " 'x_max': weave.legacy.weave.ops.date_parse(str(end_date)),\n", + " 'x_min': lambda x_max: x_max - weave.legacy.weave.ops.days(5),\n", + " 'predictions_query': weave.legacy.weave.panels.Query(\n", + " predictions,\n", + " conditions=[\n", + " lambda query_input: weave.legacy.weave.panels.QueryCondition(\n", + " expression=query_input['model_version'],\n", + " editor=weave.legacy.weave.panels.SelectEditor(\n", + " choices=query_input['model_version']\n", + " )\n", + " ),\n", + " lambda query_input: weave.legacy.weave.panels.QueryCondition(\n", + " expression=query_input['username'],\n", + " editor=weave.legacy.weave.panels.SelectEditor(\n", + " choices=query_input['username']\n", + " )\n", + " ),\n", + " ]),\n", + " 'predictions': lambda predictions_query: predictions_query.selected()\n", + " },\n", + " panels=[\n", + " weave.legacy.weave.panels.BoardPanel(\n", + " lambda predictions, x_min, x_max: wandb.TimeSeries(\n", + " predictions,\n", + " x=lambda item: item[\"timestamp\"],\n", + " label=lambda item: item[\"model_version\"],\n", + " agg=lambda preds: preds.count(),\n", + " min_x=x_min,\n", + " max_x=x_max,\n", + " mark=\"bar\",\n", + " axis_labels={\"y\": \"num_preds\"},\n", + " ),\n", + " layout=weave.legacy.weave.panels.BoardPanelLayout(x=0, y=0, w=12, h=6)\n", + " ),\n", + " weave.legacy.weave.panels.BoardPanel(\n", + " lambda predictions, x_min, x_max: wandb.TimeSeries(\n", + " predictions,\n", + " x=lambda item: item[\"timestamp\"],\n", + " label=lambda item: item[\"username\"],\n", + " agg=lambda preds: preds.count(),\n", + " min_x=x_min,\n", + " max_x=x_max,\n", + " mark=\"bar\",\n", + " axis_labels={\"y\": \"num_preds\"},\n", + " ),\n", + " layout=weave.legacy.weave.panels.BoardPanelLayout(x=12, y=0, w=12, h=6)\n", + " ),\n", + " weave.legacy.weave.panels.BoardPanel(\n", + " lambda predictions, x_min, x_max: wandb.TimeSeries(\n", + " predictions,\n", + " x=lambda item: item[\"timestamp\"],\n", + " #label=lambda item: item[\"model_version\"],\n", + " agg=lambda preds: preds[\"api_cost\"].sum(),\n", + " min_x=x_min,\n", + " max_x=x_max,\n", + " mark=\"line\",\n", + " axis_labels={\"y\": \"api_cost\"},\n", + " ),\n", + " layout=weave.legacy.weave.panels.BoardPanelLayout(x=0, y=6, w=8, h=6)\n", + " ),\n", + " weave.legacy.weave.panels.BoardPanel(\n", + " lambda predictions, x_min, x_max: wandb.TimeSeries(\n", + " predictions,\n", + " x=lambda item: item[\"timestamp\"],\n", + " #label=lambda item: item[\"model_version\"],\n", + " agg=lambda preds: preds[\"prompt_tokens\"].sum() + preds['completion_tokens'].sum(),\n", + " min_x=x_min,\n", + " max_x=x_max,\n", + " mark=\"line\",\n", + " axis_labels={\"y\": \"total_tokens\"},\n", + " ),\n", + " layout=weave.legacy.weave.panels.BoardPanelLayout(x=8, y=6, w=8, h=6)\n", + " ),\n", + " weave.legacy.weave.panels.BoardPanel(\n", + " lambda predictions, x_min, x_max: wandb.TimeSeries(\n", + " predictions,\n", + " x=lambda item: item[\"timestamp\"],\n", + " #label=lambda item: item[\"model_version\"],\n", + " agg=lambda preds: preds[\"latency\"].avg(),\n", + " min_x=x_min,\n", + " max_x=x_max,\n", + " mark=\"line\",\n", + " axis_labels={\"y\": \"avg_latency\"},\n", + " ),\n", + " layout=weave.legacy.weave.panels.BoardPanelLayout(x=16, y=6, w=8, h=6)\n", + " ),\n", + " weave.legacy.weave.panels.BoardPanel(\n", + " lambda predictions, x_min, x_max: wandb.Distribution(\n", + " predictions,\n", + " value_fn=lambda pred: pred['api_cost'],\n", + " bin_size=0.2\n", + " ),\n", + " layout=weave.legacy.weave.panels.BoardPanelLayout(x=0, y=12, w=8, h=6)\n", + " ),\n", + " weave.legacy.weave.panels.BoardPanel(\n", + " lambda predictions, x_min, x_max: wandb.Distribution(\n", + " predictions,\n", + " value_fn=lambda pred: pred['prompt_tokens'] + pred['completion_tokens'],\n", + " bin_size=25\n", + " ),\n", + " layout=weave.legacy.weave.panels.BoardPanelLayout(x=8, y=12, w=8, h=6)\n", + " ),\n", + " weave.legacy.weave.panels.BoardPanel(\n", + " lambda predictions, x_min, x_max: wandb.Distribution(\n", + " predictions,\n", + " value_fn=lambda pred: pred['latency'],\n", + " bin_size=0.05\n", + " ),\n", + " layout=weave.legacy.weave.panels.BoardPanelLayout(x=16, y=12, w=8, h=6)\n", + " ),\n", + " weave.legacy.weave.panels.BoardPanel(\n", + " lambda predictions: predictions,\n", + " layout=weave.legacy.weave.panels.BoardPanelLayout(x=0, y=18, w=24, h=12)\n", + " )\n", + " ]\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1d8bafc9", + "metadata": {}, + "outputs": [], + "source": [ + "board" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/weave/legacy/examples/experimental/MonitorPanelPlot.ipynb b/weave/legacy/examples/experimental/MonitorPanelPlot.ipynb new file mode 100644 index 00000000000..31b6a8de189 --- /dev/null +++ b/weave/legacy/examples/experimental/MonitorPanelPlot.ipynb @@ -0,0 +1,252 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "f7512dfa", + "metadata": {}, + "source": [ + "# Model monitoring dashboard example\n", + "\n", + "This notebook currently requires the plotly library to be installed.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3e7d3247", + "metadata": {}, + "outputs": [], + "source": [ + "import weave\n", + "import weave.legacy.weave.panels\n", + "weave.use_lazy_execution()\n", + "from weave.legacy.scripts import syndata_mon" + ] + }, + { + "cell_type": "markdown", + "id": "8519889f", + "metadata": {}, + "source": [ + "## Create synthetic data\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d6a3a6f1", + "metadata": {}, + "outputs": [], + "source": [ + "data = syndata_mon.random_predictions(10)\n", + "timestamps = data._arrow_data.field('timestamp')\n", + "start_date = timestamps[0].as_py().to_pydatetime()\n", + "end_date = timestamps[-1].as_py().to_pydatetime()\n", + "predictions = weave.save(data, 'predictions')" + ] + }, + { + "cell_type": "markdown", + "id": "cdd59e39", + "metadata": {}, + "source": [ + "## Create a Weave Board for the data\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "57fc86e0", + "metadata": {}, + "outputs": [], + "source": [ + "# Fetch the data by the latest alias instead of its version hash.\n", + "# TODO: change weave.save to do this automatically\n", + "predictions = weave.legacy.weave.ops.get(\"local-artifact:///predictions:latest/obj\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "25c63bf1-10f4-4a9b-b5f3-01361126bc2c", + "metadata": {}, + "outputs": [], + "source": [ + "# from weave.legacy.weave import context_state\n", + "# context_state._eager_mode.get()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8049b6dd", + "metadata": {}, + "outputs": [], + "source": [ + "# Create a dashboard for our data\n", + "board = weave.legacy.weave.panels.Board(\n", + " vars={\n", + " 'data': predictions,\n", + " 'zoom_range': weave.legacy.weave.ops.make_list(\n", + " a=weave.legacy.weave.ops.date_parse(str(start_date)),\n", + " b=weave.legacy.weave.ops.date_parse(str(end_date))),\n", + " 'data_range': lambda data: weave.legacy.weave.ops.make_list(\n", + " a=data['timestamp'].min(),\n", + " b=data['timestamp'].max()\n", + " ),\n", + " 'bin_range': lambda zoom_range, data_range: zoom_range.coalesce(data_range),\n", + " 'predictions_query': weave.legacy.weave.panels.Query(\n", + " predictions,\n", + " conditions=[\n", + " lambda query_input: weave.legacy.weave.panels.QueryCondition(\n", + " expression=query_input['model_version'],\n", + " editor=weave.legacy.weave.panels.SelectEditor(\n", + " choices=query_input['model_version']\n", + " )\n", + " ),\n", + " lambda query_input: weave.legacy.weave.panels.QueryCondition(\n", + " expression=query_input['username'],\n", + " editor=weave.legacy.weave.panels.SelectEditor(\n", + " choices=query_input['username']\n", + " )\n", + " ),\n", + " ]),\n", + " 'predictions': lambda predictions_query: predictions_query.selected()\n", + " },\n", + " panels=[\n", + " weave.legacy.weave.panels.BoardPanel(\n", + " lambda predictions, zoom_range, bin_range: weave.legacy.weave.panels.Plot(\n", + " predictions,\n", + " x=lambda item: item[\"timestamp\"].bin(\n", + " weave.legacy.weave.ops.timestamp_bins_nice(bin_range, 30)),\n", + " label=lambda item: item[\"model_version\"],\n", + " y=lambda preds: preds.count(),\n", + " groupby_dims=['x', 'label'],\n", + " domain_x=zoom_range,\n", + " mark=\"bar\",\n", + " ),\n", + " layout=weave.legacy.weave.panels.BoardPanelLayout(x=0, y=0, w=12, h=6)\n", + " ),\n", + " weave.legacy.weave.panels.BoardPanel(\n", + " lambda predictions, zoom_range, bin_range: weave.legacy.weave.panels.Plot(\n", + " predictions,\n", + " x=lambda item: item[\"timestamp\"].bin(\n", + " weave.legacy.weave.ops.timestamp_bins_nice(bin_range, 100)),\n", + " label=lambda item: item[\"username\"],\n", + " y=lambda preds: preds.count(),\n", + " groupby_dims=['x', 'label'],\n", + " domain_x=zoom_range,\n", + " mark=\"bar\",\n", + " ),\n", + " layout=weave.legacy.weave.panels.BoardPanelLayout(x=12, y=0, w=12, h=6)\n", + " ),\n", + " weave.legacy.weave.panels.BoardPanel(\n", + " lambda predictions, zoom_range, bin_range: weave.legacy.weave.panels.Plot(\n", + " predictions,\n", + " x=lambda item: item[\"timestamp\"].bin(\n", + " weave.legacy.weave.ops.timestamp_bins_nice(bin_range, 100))['start'],\n", + " y=lambda preds: preds[\"api_cost\"].sum(),\n", + " groupby_dims=['x'],\n", + " domain_x=zoom_range,\n", + " mark=\"line\",\n", + " ),\n", + " layout=weave.legacy.weave.panels.BoardPanelLayout(x=0, y=6, w=8, h=6)\n", + " ),\n", + " weave.legacy.weave.panels.BoardPanel(\n", + " lambda predictions, zoom_range, bin_range: weave.legacy.weave.panels.Plot(\n", + " predictions,\n", + " x=lambda item: item[\"timestamp\"].bin(\n", + " weave.legacy.weave.ops.timestamp_bins_nice(bin_range, 100))['start'],\n", + " y=lambda preds: preds[\"prompt_tokens\"].sum() + preds['completion_tokens'].sum(),\n", + " groupby_dims=['x'],\n", + " domain_x=zoom_range,\n", + " mark=\"line\",\n", + " ),\n", + " layout=weave.legacy.weave.panels.BoardPanelLayout(x=8, y=6, w=8, h=6)\n", + " ),\n", + " weave.legacy.weave.panels.BoardPanel(\n", + " lambda predictions, zoom_range, bin_range: weave.legacy.weave.panels.Plot(\n", + " predictions,\n", + " x=lambda item: item[\"timestamp\"].bin(\n", + " weave.legacy.weave.ops.timestamp_bins_nice(bin_range, 100))['start'],\n", + " y=lambda preds: preds[\"latency\"].avg(),\n", + " groupby_dims=['x'],\n", + " domain_x=zoom_range,\n", + " mark=\"line\",\n", + " ),\n", + " layout=weave.legacy.weave.panels.BoardPanelLayout(x=16, y=6, w=8, h=6)\n", + " ),\n", + " weave.legacy.weave.panels.BoardPanel(\n", + " lambda predictions: weave.legacy.weave.panels.Plot(\n", + " predictions,\n", + " x=lambda item: item[\"api_cost\"].bin(\n", + " weave.legacy.weave.ops.numbers_bins_equal(predictions['api_cost'], 50)),\n", + " y=lambda preds: preds.count(),\n", + " groupby_dims=['x'],\n", + " mark=\"bar\",\n", + " ),\n", + " layout=weave.legacy.weave.panels.BoardPanelLayout(x=0, y=12, w=8, h=6)\n", + " ),\n", + " weave.legacy.weave.panels.BoardPanel(\n", + " lambda predictions: weave.legacy.weave.panels.Plot(\n", + " predictions,\n", + " x=lambda item: (item['prompt_tokens'] + item['completion_tokens']).bin(\n", + " weave.legacy.weave.ops.number_bins_fixed(25)),\n", + " y=lambda preds: preds.count(),\n", + " groupby_dims=['x'],\n", + " mark=\"bar\",\n", + " ),\n", + " layout=weave.legacy.weave.panels.BoardPanelLayout(x=8, y=12, w=8, h=6)\n", + " ),\n", + " weave.legacy.weave.panels.BoardPanel(\n", + " lambda predictions: weave.legacy.weave.panels.Plot(\n", + " predictions,\n", + " x=lambda item: item['latency'].bin(\n", + " weave.legacy.weave.ops.numbers_bins_equal(predictions['latency'], 50)),\n", + " y=lambda preds: preds.count(),\n", + " groupby_dims=['x'],\n", + " mark=\"bar\",\n", + " ),\n", + " layout=weave.legacy.weave.panels.BoardPanelLayout(x=16, y=12, w=8, h=6)\n", + " ),\n", + " weave.legacy.weave.panels.BoardPanel(\n", + " lambda predictions: predictions,\n", + " layout=weave.legacy.weave.panels.BoardPanelLayout(x=0, y=18, w=24, h=12)\n", + " )\n", + " ]\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1d8bafc9", + "metadata": {}, + "outputs": [], + "source": [ + "board" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/weave/legacy/examples/experimental/Mutations.ipynb b/weave/legacy/examples/experimental/Mutations.ipynb new file mode 100644 index 00000000000..9e402df99fb --- /dev/null +++ b/weave/legacy/examples/experimental/Mutations.ipynb @@ -0,0 +1,70 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "dd93a251", + "metadata": {}, + "outputs": [], + "source": [ + "import weave" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ddcbd7da", + "metadata": {}, + "outputs": [], + "source": [ + "# Only run this cell the first time\n", + "table_data = weave.save([{'a': 5, 'b': 6, 'c': 'hello'}, {'a': 7, 'b': 9, 'c': 'goodbye'}], name='my-table:latest')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0063dcd0", + "metadata": {}, + "outputs": [], + "source": [ + "dataset = weave.legacy.weave.ops.get(\"local-artifact:///my-table:latest/obj\")\n", + "dataset" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b310adec", + "metadata": {}, + "outputs": [], + "source": [ + "# You can mutate data in the UI.\n", + "# In the panel above, switch the c column Panel to StringEditor,\n", + "# then edit one of the cells.\n", + "# To see the changes were saved, rerun cell 3." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cf92cd4e", + "metadata": {}, + "outputs": [], + "source": [ + "# Or you can mutate data from Python. You can press the \"play\" icon in the UI to make it live update.\n", + "for i in range(10):\n", + " dataset.append({'a': 0, 'b': i, 'c': 'k'})" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/weave/legacy/examples/experimental/ProductionMonitoring/MonitorPanelPlotGeneric.ipynb b/weave/legacy/examples/experimental/ProductionMonitoring/MonitorPanelPlotGeneric.ipynb new file mode 100644 index 00000000000..27ff8359257 --- /dev/null +++ b/weave/legacy/examples/experimental/ProductionMonitoring/MonitorPanelPlotGeneric.ipynb @@ -0,0 +1,400 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "f7512dfa", + "metadata": {}, + "source": [ + "# Model monitoring dashboard example\n", + "\n", + "This notebook currently requires the plotly library to be installed.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "80506dea", + "metadata": {}, + "outputs": [], + "source": [ + "import weave\n", + "from weave.legacy.scripts import syndata_mon" + ] + }, + { + "cell_type": "markdown", + "id": "8519889f", + "metadata": {}, + "source": [ + "## Create synthetic data\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7eba21e0", + "metadata": {}, + "outputs": [], + "source": [ + "#preds = syndata_mon.random_predictions(10)\n", + "\n", + "#predictions = weave.save(preds, 'predictions')\n", + "#len(preds.column('prompt').to_pylist_raw())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8934efcf", + "metadata": {}, + "outputs": [], + "source": [ + "import logging\n", + "import json\n", + "import os\n", + "from pathlib import Path\n", + "\n", + "import tiktoken\n", + "import faiss\n", + "import numpy as np\n", + "from tenacity import (\n", + " before_sleep_log,\n", + " retry,\n", + " retry_if_exception_type,\n", + " stop_after_attempt,\n", + " wait_exponential,\n", + ")\n", + "import openai\n", + "\n", + "logger = logging.getLogger(__name__)\n", + "openai.api_key = os.environ['OPENAI_API_KEY']\n", + "\n", + "# OpenAI API functions\n", + "retry_openai_decorator = retry(\n", + " reraise=True,\n", + " stop=stop_after_attempt(4),\n", + " wait=wait_exponential(multiplier=1, min=4, max=10),\n", + " retry=(\n", + " retry_if_exception_type(openai.error.Timeout)\n", + " | retry_if_exception_type(openai.error.APIError)\n", + " | retry_if_exception_type(openai.error.APIConnectionError)\n", + " | retry_if_exception_type(openai.error.RateLimitError)\n", + " | retry_if_exception_type(openai.error.ServiceUnavailableError)\n", + " ),\n", + " before_sleep=before_sleep_log(logger, logging.WARNING),\n", + " )\n", + "\n", + "@retry_openai_decorator\n", + "def openai_embed(model, input):\n", + " return openai.Embedding.create(input = input, model=model)\n", + "\n", + "@retry_openai_decorator\n", + "def openai_chatcompletion(model, messages):\n", + " return openai.ChatCompletion.create(\n", + " model=\"gpt-3.5-turbo\", # The deployment name you chose when you deployed the ChatGPT or GPT-4 model.\n", + " messages = messages\n", + " )\n", + "\n", + "# Helper to efficiently embed a set of documents using the OpenAI embedding API\n", + "# This is from langchain\n", + "\n", + "embedding_ctx_length = 8191\n", + "OPENAI_EMBEDDING_MODEL = \"text-embedding-ada-002\"\n", + "chunk_size = 1000\n", + "\n", + "from typing import List\n", + "\n", + "def embed_texts(texts: List[str], embedding_model: str) -> List[List[float]]:\n", + " embeddings: List[List[float]] = [[] for _ in range(len(texts))]\n", + " tokens = []\n", + " indices = []\n", + " encoding = tiktoken.model.encoding_for_model(embedding_model)\n", + " for i, text in enumerate(texts):\n", + " if embedding_model.endswith(\"001\"):\n", + " # See: https://github.com/openai/openai-python/issues/418#issuecomment-1525939500\n", + " # replace newlines, which can negatively affect performance.\n", + " text = text.replace(\"\\n\", \" \")\n", + " token = encoding.encode(\n", + " text,\n", + " disallowed_special=\"all\",\n", + " )\n", + " for j in range(0, len(token), embedding_ctx_length):\n", + " tokens += [token[j : j + embedding_ctx_length]]\n", + " indices += [i]\n", + "\n", + " batched_embeddings = []\n", + " _chunk_size = chunk_size\n", + " for i in range(0, len(tokens), _chunk_size):\n", + " response = openai_embed(\n", + " embedding_model,\n", + " input=tokens[i : i + _chunk_size],\n", + " )\n", + " batched_embeddings += [r[\"embedding\"] for r in response[\"data\"]]\n", + "\n", + " results: List[List[List[float]]] = [[] for _ in range(len(texts))]\n", + " num_tokens_in_batch: List[List[int]] = [[] for _ in range(len(texts))]\n", + " for i in range(len(indices)):\n", + " results[indices[i]].append(batched_embeddings[i])\n", + " num_tokens_in_batch[indices[i]].append(len(tokens[i]))\n", + "\n", + " for i in range(len(texts)):\n", + " _result = results[i]\n", + " if len(_result) == 0:\n", + " average = embed_with_retry(\n", + " embedding_model,\n", + " input=\"\",\n", + " )[\"data\"][0][\"embedding\"]\n", + " else:\n", + " average = np.average(\n", + " _result, axis=0, weights=num_tokens_in_batch[i]\n", + " )\n", + " embeddings[i] = (average / np.linalg.norm(average)).tolist()\n", + "\n", + " return embeddings" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6ab2debd", + "metadata": {}, + "outputs": [], + "source": [ + "import pandas\n", + "data = pandas.read_csv('/Users/shawn/datasets/wandb_export_2023-06-03T15_01_20.066-07_00.csv')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4c084173", + "metadata": {}, + "outputs": [], + "source": [ + "data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "eef1fb56", + "metadata": {}, + "outputs": [], + "source": [ + "embeddings = embed_texts(data['question'][:100], OPENAI_EMBEDDING_MODEL)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "45c9bcf8", + "metadata": {}, + "outputs": [], + "source": [ + "from weave.legacy.weave.ecosystem import umap" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "54a1f09d", + "metadata": {}, + "outputs": [], + "source": [ + "umap.umap_projection(embeddings, {})" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0781ddcd", + "metadata": {}, + "outputs": [], + "source": [ + "len(embeddings)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c190e190", + "metadata": {}, + "outputs": [], + "source": [ + "from sklearn.datasets import fetch_openml\n", + "from sklearn.decomposition import PCA\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "%matplotlib inline\n", + "\n", + "# Dimension reduction and clustering libraries\n", + "import umap\n", + "import hdbscan\n", + "import sklearn.cluster as cluster\n", + "from sklearn.metrics import adjusted_rand_score, adjusted_mutual_info_score" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "16e52ec2", + "metadata": {}, + "outputs": [], + "source": [ + "standard_embedding = umap.UMAP(random_state=42).fit_transform(embeddings)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c28defdb", + "metadata": {}, + "outputs": [], + "source": [ + "plt.scatter(standard_embedding[:, 0], standard_embedding[:, 1], s=0.1, cmap='Spectral');" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5e59fd77", + "metadata": {}, + "outputs": [], + "source": [ + "clusterable_embedding = umap.UMAP(\n", + " n_neighbors=30,\n", + " min_dist=0.0,\n", + " n_components=2,\n", + " random_state=42,\n", + ").fit_transform(embeddings)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d571528d", + "metadata": {}, + "outputs": [], + "source": [ + "clusterable_embedding.shape" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c568b42a", + "metadata": {}, + "outputs": [], + "source": [ + "plt.scatter(clusterable_embedding[:, 0], clusterable_embedding[:, 1], s=0.1, cmap='Spectral');" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bf0920f0", + "metadata": {}, + "outputs": [], + "source": [ + "labels = hdbscan.HDBSCAN(\n", + " min_samples=10,\n", + " min_cluster_size=50,\n", + ").fit_predict(clusterable_embedding)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ead20089", + "metadata": {}, + "outputs": [], + "source": [ + "#labels\n", + "np.unique(labels, return_counts=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "32d4fabe", + "metadata": {}, + "outputs": [], + "source": [ + "clustered = (labels >= 0)\n", + "plt.scatter(standard_embedding[~clustered, 0],\n", + " standard_embedding[~clustered, 1],\n", + " color=(0.5, 0.5, 0.5),\n", + " s=0.1,\n", + " alpha=0.5)\n", + "plt.scatter(standard_embedding[clustered, 0],\n", + " standard_embedding[clustered, 1],\n", + " c=labels[clustered],\n", + " s=0.1,\n", + " cmap='Spectral');" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "289f1c42", + "metadata": {}, + "outputs": [], + "source": [ + "data['embedding_x'] = standard_embedding[:,0]\n", + "data['embedding_y'] = standard_embedding[:,1]\n", + "data['cluster_id'] = labels.astype(str)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "aafaa6f9", + "metadata": {}, + "outputs": [], + "source": [ + "weave.show(data)" + ] + }, + { + "cell_type": "markdown", + "id": "cdd59e39", + "metadata": {}, + "source": [ + "## Create a Weave Board for the data\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "22be0bd4", + "metadata": {}, + "outputs": [], + "source": [ + "from weave.legacy.weave.panels_py import panel_autoboard\n", + "\n", + "panel_autoboard.auto_panels(predictions)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/weave/legacy/examples/experimental/ProductionMonitoring/ProductionMonitoringConceptualOverview.ipynb b/weave/legacy/examples/experimental/ProductionMonitoring/ProductionMonitoringConceptualOverview.ipynb new file mode 100644 index 00000000000..0a4bd3d3ac2 --- /dev/null +++ b/weave/legacy/examples/experimental/ProductionMonitoring/ProductionMonitoringConceptualOverview.ipynb @@ -0,0 +1,358 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "36a9f92a", + "metadata": {}, + "source": [ + "# W&B Production Monitoring Overview\n", + "\n", + "This notebook demonstrates how to monitor production models with W&B through an illustrative example. We will train a model to correctly identify handwritten digits, then monitor a locally deployed version of the model. We create a gradio app which runs in the notebook and lets a user draw/\"handwrite\" characters with the mouse and give live feedback by labeling the character as the digit 0-9.\n", + "\n", + "_Note: To keep the example focused on important code, much of the dataset manipulation, modelling, and other utilities are packaged in local files and imported here_\n", + "\n", + "# Step 0: Setup & import dependencies" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3af52605", + "metadata": {}, + "outputs": [], + "source": [ + "!pip install tensorflow\n", + "!pip install gradio\n", + "!pip install weave" + ] + }, + { + "cell_type": "markdown", + "id": "55afcd6c", + "metadata": {}, + "source": [ + "Log in to W&B to sync these examples to your W&B account, where you can view, interact with, and customize the resulting Tables and Boards." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c9e3e332", + "metadata": {}, + "outputs": [], + "source": [ + "import wandb\n", + "wandb.login()" + ] + }, + { + "cell_type": "markdown", + "id": "48354a22", + "metadata": {}, + "source": [ + "Set your W&B entity (username or team name) and optionally rename the destination project." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "424dc619", + "metadata": {}, + "outputs": [], + "source": [ + "WB_ENTITY = \"shawn\"\n", + "WB_PROJECT = \"prodmon_mnist\"" + ] + }, + { + "cell_type": "markdown", + "id": "8daaf18d", + "metadata": {}, + "source": [ + "# Step 1: Get data\n", + "In this example, we will use `keras.datasets.mnist.load_data()` to load in the MNIST dataset. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "de553636", + "metadata": {}, + "outputs": [], + "source": [ + "import model_util\n", + "\n", + "dataset = model_util.get_dataset()\n", + "model_util.image_from_array(dataset[0][0])" + ] + }, + { + "cell_type": "markdown", + "id": "6822a544", + "metadata": {}, + "source": [ + "# Step 2: Train model\n", + "Next we will train a classic NN to predict the digits" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "39bf1bc2", + "metadata": {}, + "outputs": [], + "source": [ + "model = model_util.train_model(*dataset, conv_layers=0, epochs=1) # 1 epoch so we can actually see some errors" + ] + }, + { + "cell_type": "markdown", + "id": "68a38ddd", + "metadata": {}, + "source": [ + "# Step 3: Query model\n", + "Now, let's query the model! Normally there is a little pre- and post- processing needed to make a prediction - we will write a short function to handle this for us." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5b8afe99", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import json\n", + "\n", + "def simple_predict(image_arr):\n", + " # Prepare image for model\n", + " tensor = (image_arr.astype(\"float32\")).reshape(1, 28, 28, 1)\n", + "\n", + " # Make the prediction\n", + " prediction = model.predict(tensor, verbose=False)\n", + "\n", + " # In this application, we need to reshape the output:\n", + " raw_predictions = prediction[0].tolist()\n", + " logits = {\n", + " str(k): v for k, v in zip(range(10), raw_predictions)\n", + " }\n", + " \n", + " prediction = np.argmax(raw_predictions).tolist()\n", + " \n", + " return {\"logits\": logits, \"prediction\": prediction}\n", + "\n", + "_, _, x_test, y_test = dataset\n", + "for i in range(10):\n", + " image_arr = x_test[i]\n", + " truth = y_test[i]\n", + " preds = simple_predict(image_arr)\n", + " \n", + " print(f\"Input: {truth}\")\n", + " display(model_util.image_from_array(image_arr))\n", + " print(f\"Prediction: {preds['prediction']}\")\n", + " print(f\"Logits: {json.dumps(preds['logits'], indent=2)}\")\n", + " print(\"\")\n", + " " + ] + }, + { + "cell_type": "markdown", + "id": "8826acf8", + "metadata": {}, + "source": [ + "# Step 3A: Save predictions with W&B Weave using StreamTable\n", + "With W&B's Weave library, we can stream any data to W&B for storage and further analysis." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9c59bccb", + "metadata": {}, + "outputs": [], + "source": [ + "import weave\n", + "weave.use_frontend_devmode()\n", + "from weave.legacy.weave.monitoring import StreamTable\n", + "\n", + "# Initialize a stream table\n", + "# (optionally change the name argument to any string\n", + "# that follows the wandbentity_name/project_name/table_name format)\n", + "st = StreamTable(f\"{WB_ENTITY}/{WB_PROJECT}/logged_predictions\")\n", + "_, _, x_test, y_test = dataset\n", + "for i in range(100):\n", + " image_arr = x_test[i]\n", + " truth = y_test[i].tolist()\n", + " preds = simple_predict(image_arr)\n", + " \n", + " # Log the data\n", + " st.log({\n", + " **preds,\n", + " \"image\": model_util.image_from_array(image_arr),\n", + " \"truth\": truth\n", + " })\n", + "\n", + "# Optional: wait for the logs to finish uploading (nicer for live demos)\n", + "st.finish()\n", + "\n", + "# Show the StreamTable\n", + "st " + ] + }, + { + "cell_type": "markdown", + "id": "dcb0cc29", + "metadata": {}, + "source": [ + "# Step 3B: Save predictions with W&B Weave using `monitor` decorator\n", + "This pattern of logging inputs and outputs of a functions is so common, that we provide a decorator which automatically logs a function's I/O." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "90ab5fe4", + "metadata": {}, + "outputs": [], + "source": [ + "from weave.legacy.weave.monitoring import monitor\n", + "import numpy as np\n", + "\n", + "mon = monitor.init_monitor(f\"{WB_ENTITY}/{WB_PROJECT}/monitor_predict_function\")\n", + "\n", + "def preprocess(span):\n", + " span.inputs['image'] = model_util.image_from_array(span.inputs['image_arr'])\n", + " del span.inputs['image_arr']\n", + "\n", + "@mon.trace(\n", + " # An preprocessor allows the function arguments to be pre-processed before logging.\n", + " preprocess = preprocess\n", + ")\n", + "def monitor_predict(image_arr):\n", + " # Prepare image for model\n", + " tensor = (image_arr.astype(\"float32\")).reshape(1, 28, 28, 1)\n", + "\n", + " # Make the prediction\n", + " prediction = model.predict(tensor, verbose=False)\n", + "\n", + " # In this application, we need to reshape the output:\n", + " raw_predictions = prediction[0].tolist()\n", + " logits = {\n", + " str(k): v for k, v in zip(range(10), raw_predictions)\n", + " }\n", + " \n", + " prediction = np.argmax(raw_predictions).tolist()\n", + " \n", + " return {\"logits\": logits, \"prediction\": prediction}\n", + "\n", + "_, _, x_test, y_test = dataset\n", + "for i in range(100):\n", + " image_arr = x_test[i]\n", + " truth = y_test[i].tolist()\n", + " # Use the added monitor_attributes argument to add additional data\n", + " preds = monitor_predict(image_arr, monitor_attributes={'truth': truth})" + ] + }, + { + "cell_type": "markdown", + "id": "d3896993", + "metadata": {}, + "source": [ + "# Step 4: End-to-end example\n", + "Typically a production application will contain a prediction service that provides predictions to a client. To demonstrate this in a notebook, we will create a `PredictionService` and an `AppUI`: a small interface which lets the user to draw an image, view the prediction, and give feedback on a result (in this case, correctly label a handdrawn digit 0-9). These communicate via `predict` and `record_feedback` methods. \n", + "\n", + "Note: this is purely for example purposes—your production systems may widely vary in structure_" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "efc97d6a", + "metadata": {}, + "outputs": [], + "source": [ + "# TODO: not yet working with new API\n", + "\n", + "# import app_util\n", + "# from weave.legacy.weave.monitoring import monitor\n", + "# import PIL\n", + "# import numpy as np\n", + "\n", + "# class PredictionService(app_util.PredictionServiceInterface):\n", + "# def __init__(self, model):\n", + "# self.model = model\n", + "# self.last_prediction = {}\n", + " \n", + "# @monitor(auto_log = False, entity_name=WB_ENTITY, project_name=WB_PROJECT)\n", + "# def _raw_predict(self, pil_image: PIL.Image) -> dict:\n", + "# # Prepare image for model\n", + "# tensor = (np.array(pil_image.resize((28, 28))).astype(\"float32\") / 255).reshape(1, 28, 28, 1)\n", + "\n", + "# # Make the prediction\n", + "# prediction = self.model.predict(tensor, verbose=False)\n", + "\n", + "# # In this application, we need to reshape the output:\n", + "# raw_predictions = prediction[0].tolist()\n", + "# logits = {\n", + "# str(k): v for k, v in zip(range(10), raw_predictions)\n", + "# }\n", + "\n", + "# prediction = np.argmax(raw_predictions).tolist()\n", + "\n", + "# return {\"logits\": logits, \"prediction\": prediction}\n", + " \n", + "# def _update_last_prediction(self, prediction) -> None:\n", + "# if len(self.last_prediction) > 0:\n", + "# last_pred = self.last_prediction.pop(list(self.last_prediction.keys())[0])\n", + "# last_pred.finalize()\n", + "# self.last_prediction[prediction.id] = prediction\n", + "\n", + " \n", + "# def predict(self, pil_image: PIL.Image) -> app_util.Prediction:\n", + "# record = self._raw_predict(pil_image)\n", + " \n", + "# # Cache the last prediction for ground_truth recording\n", + "# self._update_last_prediction(record)\n", + " \n", + "# # Return the prediction\n", + "# return app_util.Prediction(record.get()['logits'], record.id)\n", + " \n", + "# def record_feedback(self, prediction_id: str, feedback: int) -> None:\n", + "# if prediction_id not in self.last_prediction:\n", + "# return\n", + "\n", + "# # Get the past prediction\n", + "# prediction = self.last_prediction.pop(prediction_id)\n", + " \n", + "# # Save the user feedback\n", + "# prediction.add_data({'user_feedback': feedback})\n", + " \n", + "# # Log the results\n", + "# prediction.finalize()\n", + " \n", + "# app_util.render_app(PredictionService(model))" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/experimental/ProductionMonitoring/StreamTable.md b/weave/legacy/examples/experimental/ProductionMonitoring/StreamTable.md similarity index 98% rename from examples/experimental/ProductionMonitoring/StreamTable.md rename to weave/legacy/examples/experimental/ProductionMonitoring/StreamTable.md index c2878b9e37c..b353164c22d 100644 --- a/examples/experimental/ProductionMonitoring/StreamTable.md +++ b/weave/legacy/examples/experimental/ProductionMonitoring/StreamTable.md @@ -11,7 +11,7 @@ A Weave StreamTable object enables continuous streaming of data from an applicat The only required argument to create a StreamTable is the name of the StreamTable object. ```python -from weave.legacy.monitoring import StreamTable +from weave.legacy.weave.monitoring import StreamTable st = StreamTable("my_entity_name/my_project_name/my_table_name") ``` diff --git a/examples/experimental/ProductionMonitoring/app_util.py b/weave/legacy/examples/experimental/ProductionMonitoring/app_util.py similarity index 100% rename from examples/experimental/ProductionMonitoring/app_util.py rename to weave/legacy/examples/experimental/ProductionMonitoring/app_util.py diff --git a/examples/experimental/ProductionMonitoring/model_util.py b/weave/legacy/examples/experimental/ProductionMonitoring/model_util.py similarity index 100% rename from examples/experimental/ProductionMonitoring/model_util.py rename to weave/legacy/examples/experimental/ProductionMonitoring/model_util.py diff --git a/weave/legacy/examples/experimental/ProductionMonitoring/stream_table_api.ipynb b/weave/legacy/examples/experimental/ProductionMonitoring/stream_table_api.ipynb new file mode 100644 index 00000000000..6b106621520 --- /dev/null +++ b/weave/legacy/examples/experimental/ProductionMonitoring/stream_table_api.ipynb @@ -0,0 +1,160 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "eccb44d3", + "metadata": {}, + "source": [ + "# Weave StreamTable Usage\n", + "\n", + "This notebook demonstrates basic Weave StreamTable usage with interactive examples.\n", + "\n", + "## Step 0: Setup\n", + "\n", + "All the StreamTables created in this notebook will be saved to the WB_PROJECT under the WB_ENTITY account on the public W&B cloud. \n", + "\n", + "**Please login to W&B and set your WB_ENTITY** before running this demo. If entity is not provided explicitly, this will attempt to default to the current logged-in entity if one is available." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3e034bf1", + "metadata": {}, + "outputs": [], + "source": [ + "!pip install weave\n", + "import wandb\n", + "wandb.login()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "51c30e7a", + "metadata": {}, + "outputs": [], + "source": [ + "import weave\n", + "from weave.legacy.weave.monitoring import StreamTable" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5684f0b8", + "metadata": {}, + "outputs": [], + "source": [ + "STREAM_TABLE_NAME = \"my_stream_table\"\n", + "WB_PROJECT = \"mesa\"\n", + "WB_ENTITY = \"\"" + ] + }, + { + "cell_type": "markdown", + "id": "6f676d96", + "metadata": {}, + "source": [ + "## Step 1: Define a StreamTable\n", + "\n", + "StreamTable has a single required argument: the name of the StreamTable object.\n", + "\n", + "```python\n", + "st = StreamTable(\"stacey/mesa/my_stream_table\")\n", + "```\n", + "\n", + "This takes the form `my_wb_entity/my_wb_project_name/my_stream_table_name` where you can modify the component names to the relevant strings (e.g. your W&B username or shared W&B team name, a new or existing W&B project name).\n", + "\n", + "## Step 2: Log some data\n", + "\n", + "To add rows to the StreamTable, call `.log()` on the StreamTable object. \n", + "`.log()` accepts a single dictionary or a list of dictionaries, where each dictionary entry corresponds to one row of the table. In each dictionary, the keys are column names and the values are the corresponding cell values.\n", + "\n", + "```python\n", + "st.log({\"one_column_name\" : \"value_a\", \"another_column_name\" : 7})\n", + "st.log([{\"one_column_name\" : \"value_b\", \"another_column_name\" : 19},\n", + " {\"one_column_name\" : \"value_c\", \"another_column_name\" : 28},\n", + " {\"one_column_name\" : \"value_d\", \"another_column_name\" : 36}]\n", + "```\n", + "\n", + "The first call to `.log()` will return a Weave Panel URL, where you can view, edit, and save the resulting StreamTable as a Weave Board, of the form:\n", + "\n", + "View data at: https://weave.wandb.ai/?exp=get%28%0A++++%22wandb-artifact%3A%2F%2F%2Fstacey%2Fmesa%2Fmy_stream_table%3Alatest%2Fobj%22%29%0A++.rows" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b928c2e0", + "metadata": {}, + "outputs": [], + "source": [ + "st = StreamTable(f\"{WB_ENTITY}/{WB_PROJECT}/{STREAM_TABLE_NAME}\")\n", + "\n", + "# log data to the StreamTable as a dictionary or list of dictionaries\n", + "st.log({\"col_a\" : \"1\", \"col_b\" : \"17\", \"col_c\" : \"42\"})\n", + "\n", + "# show the StreamTable\n", + "st" + ] + }, + { + "cell_type": "markdown", + "id": "83cec916", + "metadata": {}, + "source": [ + "All log calls on a given StreamTable instance will append the given rows to that instance.\n", + "\n", + "In a notebook, the StreamTable variable on a line by itself will return a Weave Panel view of the StreamTable. The StreamTable will contain all the logged columns and their values, as well as a `timestamp` column indicating when the row was logged. By default, rows will be ordered by oldest first. You can modify a StreamTable Panel from the UI to sort by columns, group by column values, filter for specific ranges or values, etc.\n", + "\n", + "**Note:** If you would like to customize and save a specific view of a StreamTable Panel, open the StreamTable Panel in a new window as a Board and edit/save a Board from this seed panel. There are two options to achieve this:\n", + "* via the weave.wandb.ai/?exp=... URL\n", + "* via \"Open in new tab\" arrow button, revealed in the menu when you hover on the right side of a StreamTable panel displayed in the notebok)\n", + "\n", + "## Step 3: Log more data & explore the results!\n", + "\n", + "Continue logging as much data as you like to any StreamTable instance. You can keep a reference to a given Python StreamTable object in your notebook session or script, and you can reconnect to the same StreamTable instance across multiple sessions/runs of your script via the StreamTable's unique name (e.g. `st = StreamTable(\"stacey/mesa/my_stream_table\")` ) and keep adding rows. Multiple/parallel processes writing to the same StreamTable are also supported—the server will use a queue to order any concurrent messages.\n", + "\n", + "If you save the StreamTable Panel as a Board, the Board will continue to update as you send more data to the same StreamTable instance." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "294d68c0", + "metadata": {}, + "outputs": [], + "source": [ + "st.log({\"col_a\" : 5, \"col_b\" : -24, \"col_c\" : \"hello\"})\n", + "st.log([{\"col_a\" : 255, \"col_b\" : 3.1415926, \"col_c\" : \"hi!\"}])\n", + "\n", + "# optional: wait for all the rows to finish logging before loading\n", + "st.finish()\n", + "\n", + "st" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/weave/legacy/examples/experimental/Table Summary Panel.ipynb b/weave/legacy/examples/experimental/Table Summary Panel.ipynb new file mode 100644 index 00000000000..75897f7f465 --- /dev/null +++ b/weave/legacy/examples/experimental/Table Summary Panel.ipynb @@ -0,0 +1,95 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "e9f32c0f", + "metadata": {}, + "outputs": [], + "source": [ + "import weave\n", + "# Weave package now defaults to eager mode, but lazy mode required for this example notebook for now.\n", + "weave.use_lazy_execution()\n", + "import xgboost\n", + "from weave.legacy.weave.ecosystem import sklearn" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "50ff1335", + "metadata": {}, + "outputs": [], + "source": [ + "dataset = weave.use(sklearn.ca_housing_dataset(1))\n", + "dataset_arrow = weave.legacy.weave.ops.dataframe_to_arrow(dataset)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1f9dcd73", + "metadata": {}, + "outputs": [], + "source": [ + "import typing\n", + "\n", + "@weave.op()\n", + "def table_summary(table: typing.Any) -> list[weave.legacy.weave.panels.Panel]:\n", + " if not table:\n", + " # TODO: type\n", + " return\n", + " col_names = list(table[0].keys())\n", + " with weave.legacy.weave.context_state.lazy_execution():\n", + " cols = {col_name: weave.legacy.weave.ops.legacy.ops_arrow.dict.pick(table, col_name) for col_name in col_names}\n", + " panels = []\n", + " for col_name, col_values in cols.items():\n", + " panels.append(weave.legacy.weave.panels.LabeledItem(item=col_values, label=col_name))\n", + " return panels" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5d464ef4", + "metadata": {}, + "outputs": [], + "source": [ + "data = weave.save(dataset_arrow, 'my-data')\n", + "table_summary(dataset_arrow)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "47c4cdd9", + "metadata": {}, + "outputs": [], + "source": [ + "# Currently broken\n", + "#weave.publish(table_summary)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/weave/legacy/examples/experimental/app/Embeddings.ipynb b/weave/legacy/examples/experimental/app/Embeddings.ipynb new file mode 100644 index 00000000000..221d85d77b9 --- /dev/null +++ b/weave/legacy/examples/experimental/app/Embeddings.ipynb @@ -0,0 +1,123 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "d3f10990", + "metadata": {}, + "source": [ + "## Embedding explorer\n", + "\n", + "- Embeds a string column using OpenAI Embeddings\n", + "- Follows the clustering / projection approach from here: https://umap-learn.readthedocs.io/en/latest/clustering.html" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "13a3497f", + "metadata": {}, + "outputs": [], + "source": [ + "import pandas\n", + "import weave\n", + "from weave.legacy.weave.ecosystem import openai\n", + "from weave.legacy.weave.ecosystem import umap\n", + "from weave.legacy.weave.ecosystem import hdbscan" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e68f6c86", + "metadata": {}, + "outputs": [], + "source": [ + "#raw_data = pandas.read_csv('/Users/shawn/datasets/wandb_export_2023-06-03T15_01_20.066-07_00.csv')\n", + "raw_data = pandas.read_csv('wandb_export_2023-07-25T11_43_18.362-04_00.csv')\n", + "\n", + "#raw_data['request_timestamp'] = pandas.to_datetime(raw_data['request_timestamp'], unit='ms', utc=True)\n", + "\n", + "data = weave.save(weave.legacy.weave.ops.dataframe_to_arrow(raw_data), 'data')\n", + "# from weave.legacy.weave.panels_py import panel_autoboard\n", + "# panel_autoboard.auto_panels(data)\n", + "#data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "500e19b1", + "metadata": {}, + "outputs": [], + "source": [ + "data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e194b3ce", + "metadata": {}, + "outputs": [], + "source": [ + "weave.legacy.weave.panels.Board(\n", + " vars={\n", + " 'data': data,\n", + " 'limit': 1005,\n", + " 'limited_data': lambda data, limit: data.limit(limit),\n", + " 'embeddings': lambda limited_data: openai.openai_embed(limited_data['question'], {\"model\": \"text-embedding-ada-002\"}),\n", + " 'clusterable_projection': lambda embeddings: umap.umap_project(\n", + " embeddings, {\n", + " 'n_neighbors': 30,\n", + " 'min_dist': 0,\n", + " 'n_components': 2,\n", + " }\n", + " ),\n", + " 'clusters': lambda clusterable_projection: hdbscan.hdbscan_cluster(clusterable_projection, {\n", + " 'min_samples': 10,\n", + " 'min_cluster_size': 50\n", + " }),\n", + " 'projection': lambda embeddings: umap.umap_project(embeddings, {})\n", + " },\n", + " panels=[\n", + " weave.legacy.weave.panels.BoardPanel(\n", + " lambda limited_data, projection, clusters: weave.legacy.weave.panels.Plot(\n", + " limited_data,\n", + " x=lambda row, index: projection[index][0],\n", + " y=lambda row, index: projection[index][1],\n", + " label=lambda row, index: clusters[index],\n", + " ),\n", + " id='projection_plot',\n", + " layout=weave.legacy.weave.panels.BoardPanelLayout(x=0, y=0, w=24, h=12)\n", + " ),\n", + " weave.legacy.weave.panels.BoardPanel(\n", + " lambda projection_plot: projection_plot.selected_data(),\n", + " layout=weave.legacy.weave.panels.BoardPanelLayout(x=0, y=0, w=24, h=12)\n", + " )\n", + " ]\n", + ")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/weave/legacy/examples/experimental/app/RunChain.ipynb b/weave/legacy/examples/experimental/app/RunChain.ipynb new file mode 100644 index 00000000000..02b02a89b63 --- /dev/null +++ b/weave/legacy/examples/experimental/app/RunChain.ipynb @@ -0,0 +1,177 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "d6a6e7cf", + "metadata": {}, + "source": [ + "## Example of plotting RunChains\n", + "\n", + "As described here: https://github.com/wandb/runchain" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bd34fae3", + "metadata": {}, + "outputs": [], + "source": [ + "import weave\n", + "from weave.legacy.weave.ecosystem import wandb" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "baa0a8ac", + "metadata": {}, + "outputs": [], + "source": [ + "# big project, maybe 41 deep\n", + "#chain = wandb.run_chain('shawn/branchtest_400_200_1000/2ji8emz2')\n", + "\n", + "# big project, 61 deep\n", + "#chain = wandb.run_chain('shawn/branchtest_400_200_1000/sf0vvene')\n", + "\n", + "# big project, 141 deep\n", + "#chain = wandb.run_chain('shawn/branchtest_400_200_1000/hja8k932')\n", + "\n", + "# 3 deep, 200 metrics, 10k steps each (the default, this is fast for CI)\n", + "chain = wandb.run_chain('shawn/run_chain_20_200_10000/h4vz7n29')\n", + "\n", + "# 14 deep, 200 metrics, 10k steps each\n", + "#chain = wandb.run_chain('shawn/run_chain_20_200_10000/6wdyzlq9')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0431fff0", + "metadata": {}, + "outputs": [], + "source": [ + "# display the chain information\n", + "chain" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4fd52674", + "metadata": {}, + "outputs": [], + "source": [ + "# or fetch the chain object\n", + "weave.use(chain)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ee1bbb90", + "metadata": {}, + "outputs": [], + "source": [ + "weave.use(chain.segments.count())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "55b0b5c3", + "metadata": {}, + "outputs": [], + "source": [ + "# view the chain's metrics table\n", + "chain.history()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "67caf99a", + "metadata": {}, + "outputs": [], + "source": [ + "# Display plots from a RunChain. You need to do some resizing to seem them all currently.\n", + "\n", + "def plot_metric(run, metric_name):\n", + " return weave.legacy.weave.panels.Plot(\n", + " run.history(),\n", + " series=[\n", + " # Use two plot series, one for the avg line, and one for\n", + " # min/max area. We could reduce duplication here a bit more\n", + " # by updating the Plot API to allow common features of series\n", + " # to be factored out.\n", + " weave.legacy.weave.panels.Series(\n", + " run.history(),\n", + " select_functions={\n", + " 'x': lambda row, index: index.bin(\n", + " weave.legacy.weave.ops.numbers_bins_equal(\n", + " weave.legacy.weave.ops.make_list(a=0, b=run.history().count()), 250))[\"start\"],\n", + " 'y': lambda row: row[metric_name].avg()\n", + " },\n", + " groupby_dims=['x'],\n", + " constants=weave.legacy.weave.panels.PlotConstants(\n", + " mark='line'\n", + " )\n", + " ),\n", + " weave.legacy.weave.panels.Series(\n", + " run.history(),\n", + " select_functions={\n", + " 'x': lambda row, index: index.bin(\n", + " weave.legacy.weave.ops.numbers_bins_equal(\n", + " weave.legacy.weave.ops.make_list(a=0, b=run.history().count()), 250))[\"start\"],\n", + " 'y': lambda row: row[metric_name].min(),\n", + " 'y2': lambda row: row[metric_name].max(),\n", + " },\n", + " groupby_dims=['x'],\n", + " constants=weave.legacy.weave.panels.PlotConstants(\n", + " mark='area'\n", + " )\n", + " )\n", + " ],\n", + " )\n", + "\n", + "board = weave.legacy.weave.panels.Board(\n", + " vars={\n", + " 'run_chain': chain,\n", + " },\n", + " panels=[\n", + " weave.legacy.weave.panels.BoardPanel(\n", + " lambda run_chain: weave.legacy.weave.panels.Group(\n", + " layoutMode='flow',\n", + " #gridConfig=bank.flow_nxn(2, 3),\n", + " items={k: plot_metric(run_chain, k) for k in ['metric' + str(i) for i in range(10)]}\n", + " ),\n", + " layout=weave.legacy.weave.panels.BoardPanelLayout(x=0, y=0, w=24, h=12)\n", + " )\n", + " ]\n", + ")\n", + "board" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/weave/legacy/examples/experimental/app/beautiful_runs.ipynb b/weave/legacy/examples/experimental/app/beautiful_runs.ipynb new file mode 100644 index 00000000000..d1b168c35e2 --- /dev/null +++ b/weave/legacy/examples/experimental/app/beautiful_runs.ipynb @@ -0,0 +1,161 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "632b052f", + "metadata": {}, + "source": [ + "## Beautiful run plot rendering\n", + "\n", + "This notebook shows how to create a Weave Board with W&B workspace-like behavior.\n", + "\n", + "We group data along the x-axis into 250 bins, and exactly compute the min / max / avg of each bucket. Unlike the W&B workspace, this computation is exact because it happens on unsampled data in the Weave engine. Only the final grouped result is sent back to the UI.\n", + "\n", + "You can zoom on any of the plots by dragging a box. All plots are zoom synchronized using Weave 2-way binding, and all plots automatically rebin for the new window.\n", + "\n", + "We've also factored out a \"groupby\" variable so you can group by run parameters from the varbar. You can change it to 'id' or 'config.b' to try other groupings.\n", + "\n", + "All of this behavior is complete defined by the code below.\n", + "\n", + "_TODO: There is one major outstanding issue here: we're using the start of the bucket for the x-coordinate\n", + "instead of the midpoint! Fix this!_\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d2492048", + "metadata": {}, + "outputs": [], + "source": [ + "import weave\n", + "from weave.legacy.scripts import syndata" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "af8e89a5", + "metadata": {}, + "outputs": [], + "source": [ + "# Generate synthetic run data for now\n", + "runs = weave.save(syndata.random_runs(10, 10000, 10), 'runs')\n", + "# Doing this unnest operation in the UI doesn't work (like in the varbar). I don't remember\n", + "# if the types are wrong, or what the specific situation is.\n", + "# TODO: Figure out why we have to do this!\n", + "unnested_runs = weave.save(weave.use(runs.unnest()), 'random_runs_unnested')\n", + "#unnested_runs" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "69f0792f", + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "def plot_for_metric(metric_name):\n", + " return weave.legacy.weave.panels.BoardPanel(\n", + " lambda runs, groupby, zoom_domain_x, bin_domain_x: weave.legacy.weave.panels.Plot(\n", + " runs,\n", + " series=[\n", + " # Use two plot series, one for the avg line, and one for\n", + " # min/max area. We could reduce duplication here a bit more\n", + " # by updating the Plot API to allow common features of series\n", + " # to be factored out.\n", + " weave.legacy.weave.panels.Series(\n", + " runs,\n", + " select_functions={\n", + " 'x': lambda row: row[\"history.step\"].bin(\n", + " weave.legacy.weave.ops.numbers_bins_equal(bin_domain_x, 250))[\"start\"],\n", + " 'label': lambda row: row[groupby],\n", + " 'y': lambda row: row[\"history\"][metric_name].avg()\n", + " },\n", + " groupby_dims=['x', 'label'],\n", + " constants=weave.legacy.weave.panels.PlotConstants(\n", + " mark='line'\n", + " )\n", + " ),\n", + " weave.legacy.weave.panels.Series(\n", + " runs,\n", + " select_functions={\n", + " 'x': lambda row: row[\"history.step\"].bin(\n", + " weave.legacy.weave.ops.numbers_bins_equal(bin_domain_x, 250))[\"start\"],\n", + " 'label': lambda row: row[groupby],\n", + " 'y': lambda row: row[\"history\"][metric_name].min(),\n", + " 'y2': lambda row: row[\"history\"][metric_name].max(),\n", + " },\n", + " groupby_dims=['x', 'label'],\n", + " constants=weave.legacy.weave.panels.PlotConstants(\n", + " mark='area'\n", + " )\n", + " )\n", + " ],\n", + " domain_x=zoom_domain_x,\n", + " ),\n", + " #layout=weave.legacy.weave.panels.BoardPanelLayout(x=x, y=0, w=12, h=6)\n", + " )\n", + "\n", + "board = weave.legacy.weave.panels.Board(\n", + " vars={\n", + " 'runs': unnested_runs,\n", + " 'groupby': 'config.a',\n", + " # This holds the zoom boundaries, and is two-way synchronized with the plot zooms\n", + " 'zoom_domain_x': None,\n", + " # This is the extent of the step field in our data (the minimum and maxium values)\n", + " 'step_domain': lambda runs: weave.legacy.weave.ops.make_list(\n", + " a=weave.legacy.weave.ops.numbers_min(runs['history']['step']),\n", + " b=weave.legacy.weave.ops.numbers_max(runs['history']['step'])),\n", + " # The \"domain\" used for binning, which is the zoom range if its non-null, otherwise\n", + " # the step_extent\n", + " 'bin_domain_x': lambda zoom_domain_x, step_domain: zoom_domain_x.coalesce(step_domain)\n", + " },\n", + " # TODO: Use EachColumn to do this automatically for all metrics.\n", + " panels=[\n", + " plot_for_metric('metric0'),\n", + " plot_for_metric('metric1'),\n", + " plot_for_metric('metric2'),\n", + " plot_for_metric('metric3'),\n", + " plot_for_metric('metric4'),\n", + " plot_for_metric('metric5'),\n", + " plot_for_metric('metric6'),\n", + " plot_for_metric('metric7'),\n", + " ]\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3f157310", + "metadata": {}, + "outputs": [], + "source": [ + "board" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/weave/legacy/examples/experimental/app/scenario_compare.ipynb b/weave/legacy/examples/experimental/app/scenario_compare.ipynb new file mode 100644 index 00000000000..338ef5ae813 --- /dev/null +++ b/weave/legacy/examples/experimental/app/scenario_compare.ipynb @@ -0,0 +1,176 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "67551526", + "metadata": {}, + "outputs": [], + "source": [ + "import random\n", + "import weave\n", + "# Weave package now defaults to eager mode, but lazy mode required for this example notebook for now.\n", + "weave.use_lazy_execution()\n", + "from weave.legacy.weave.ecosystem import scenario" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6e12d2d0", + "metadata": {}, + "outputs": [], + "source": [ + "# a = weave.save(5)\n", + "# b = a + 2\n", + "# weave.type_of(b)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4882d74b", + "metadata": {}, + "outputs": [], + "source": [ + "import math\n", + "def make_run(n_scenarios, n) -> list[scenario.ScenarioResult]:\n", + " return [\n", + " scenario.ScenarioResult(\n", + " scenario_id=str(i),\n", + " metric1=random.expovariate(n / 33),\n", + " metric2=random.gauss(42, n / 100),\n", + " metric3=random.triangular(n, 11),\n", + " metric4=random.random() * random.random() * n,\n", + " metric5=random.random(),\n", + " metric6=random.betavariate(0.01, n / 2),\n", + " )\n", + " for i in range(n_scenarios)\n", + " ]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "218e28e2", + "metadata": {}, + "outputs": [], + "source": [ + "#panel_input = weave.save({'baseline': make_run(10), 'candidate': make_run(10)}, name='panel_input')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "984bcb3c", + "metadata": {}, + "outputs": [], + "source": [ + "#panel_input" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7e1890ca", + "metadata": {}, + "outputs": [], + "source": [ + "# make some runs\n", + "\n", + "weave.save(make_run(100, 1), name='run1:latest')\n", + "weave.save(make_run(100, 2), name='run2:latest')\n", + "weave.save(make_run(100, 3), name='run3:latest')\n", + "weave.save(make_run(100, 4), name='run4:latest')\n", + "weave.save(make_run(100, 5), name='run5:latest')\n", + "pass" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9f2b3a72", + "metadata": {}, + "outputs": [], + "source": [ + "# Ugly. We need to compute the Weave type right now.\n", + "# TODO: Fix\n", + "from weave.legacy.weave import infer_types\n", + "run_type = weave.types.List(infer_types.python_type_to_type(scenario.ScenarioResult))\n", + "\n", + "import time\n", + "runs = weave.legacy.weave.ops.objects(run_type, 'latest', int(time.time()))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "89e4b718", + "metadata": {}, + "outputs": [], + "source": [ + "panel = weave.legacy.weave.panels.Group(\n", + " preferHorizontal=True,\n", + " items={\n", + " \"sidebar\": weave.legacy.weave.panels.Group(\n", + " style=\"width: 200px; padding: 16px;\",\n", + " items={\n", + " \"baseline\": weave.legacy.weave.panels.ObjectPicker(runs, label='baseline'),\n", + " \"candidate\": weave.legacy.weave.panels.ObjectPicker(runs, label='candidate')\n", + " }\n", + " ),\n", + " \"main\": lambda sidebar: weave.legacy.weave.panels.Group(\n", + " items={\n", + " \"tables\": weave.legacy.weave.panels.Group(\n", + " style=\"height: 300px;\",\n", + " preferHorizontal=True,\n", + " equalSize=True,\n", + " items={\n", + " \"baseline_table\": weave.legacy.weave.panels.LabeledItem(\n", + " label=\"Baseline Table\",\n", + " item=weave.legacy.weave.ops.execute(sidebar.config.items['baseline'].config.choice).get()\n", + " ),\n", + " \"candidate_table\": weave.legacy.weave.panels.LabeledItem(\n", + " label=\"Candidate Table\",\n", + " item=weave.legacy.weave.ops.execute(sidebar.config.items['candidate'].config.choice).get()\n", + " )\n", + " }),\n", + " \"plots\": \n", + " weave.legacy.weave.panels.LabeledItem(\n", + " label='Scenario plots',\n", + " item=scenario.MetricsBankPanel(\n", + " weave.legacy.weave.ops.dict_(\n", + " baseline=weave.legacy.weave.ops.execute(sidebar.config.items['baseline'].config.choice).get(),\n", + " candidate=weave.legacy.weave.ops.execute(sidebar.config.items['candidate'].config.choice).get()\n", + " ))\n", + " )\n", + "\n", + " }\n", + " )\n", + " })\n", + "panel" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/weave/legacy/examples/experimental/bert_viz.ipynb b/weave/legacy/examples/experimental/bert_viz.ipynb new file mode 100644 index 00000000000..869a4961b13 --- /dev/null +++ b/weave/legacy/examples/experimental/bert_viz.ipynb @@ -0,0 +1,71 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "f4fcc7fa", + "metadata": {}, + "source": [ + "# BertViz + HuggingFace: Visualize Attention for Language Models\n", + "\n", + "Integrates the amazing [bertviz package](https://github.com/jessevig/bertviz) to visualize the attention for a provided language model (here loaded in from Hugging Face).\n", + "\n", + "## Settings to explore\n", + "\n", + "* model name: select a different model from HuggingFace (or add your own :)\n", + "* input text: edit the text sample on which to visualize attention \n", + "* visualization type: try toggling the output panel type to see the full attention map across heads and layers :)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d85f7aaa", + "metadata": { + "execution": { + "iopub.execute_input": "2023-06-02T01:46:20.864046Z", + "iopub.status.busy": "2023-06-02T01:46:20.863903Z", + "iopub.status.idle": "2023-06-02T01:46:26.125190Z", + "shell.execute_reply": "2023-06-02T01:46:26.124715Z" + } + }, + "outputs": [], + "source": [ + "import weave\n", + "# Weave package now defaults to eager mode, but lazy mode required for this example notebook for now.\n", + "weave.use_lazy_execution()\n", + "from weave.legacy.weave.ecosystem.huggingface import huggingface\n", + "\n", + "# Call a hugging face model\n", + "hf_model = huggingface().model('gpt2')\n", + "model_output = hf_model.call('The cat sat on the mat')\n", + "\n", + "# The type of .attention() is huggingface.ModelOutputType. Our bertviz panel is\n", + "# registered as the only handler for that type, so it will automatically get rendered.\n", + "\n", + "# model_output.attention()\n", + "model_output" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/weave/legacy/examples/experimental/composite_histograms.ipynb b/weave/legacy/examples/experimental/composite_histograms.ipynb new file mode 100644 index 00000000000..e74e0c47130 --- /dev/null +++ b/weave/legacy/examples/experimental/composite_histograms.ipynb @@ -0,0 +1,136 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "d70e6c48", + "metadata": {}, + "outputs": [], + "source": [ + "import weave\n", + "from weave.legacy.weave.ecosystem import lens" + ] + }, + { + "cell_type": "markdown", + "id": "c3d89416", + "metadata": {}, + "source": [ + "# Composite Histograms\n", + "\n", + "Let's plot some histograms.\n", + "\n", + "## Base case: One series, no customization" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e739d513", + "metadata": {}, + "outputs": [], + "source": [ + "lens.histogram([[1.0, 2, 3, -1.4]], 1.0, \"Composite Histogram\", ['series'])" + ] + }, + { + "cell_type": "markdown", + "id": "81fd5769", + "metadata": {}, + "source": [ + "## Two series with customization" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "301be405", + "metadata": {}, + "outputs": [], + "source": [ + "lens.histogram([[-1.8, -0.4, 0.1, -0.9, -0.77, 0.5, 0.7, 0.2, 0.1, 0.8],[0.9, 0.4, 0.5, 0.6, 1.2, 1.5, 1.6, 1.7, 2, 2.4, 2.3, 2.1]], 0.5, \"My Histogram\", [\"series A\", \"series B\"])" + ] + }, + { + "cell_type": "markdown", + "id": "ee794dd4", + "metadata": {}, + "source": [ + "## Three series" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fba3acec", + "metadata": {}, + "outputs": [], + "source": [ + "lens.histogram([[1.0, 2, 3, -1.4],[8.1, 2.2, 3.3, 5.6, 1.1], [1.6, 1.7, 1.8, -2.0, 5]], 0.5, 'Composite histogram', ['a', 'b', 'c'])" + ] + }, + { + "cell_type": "markdown", + "id": "ac8ee3eb", + "metadata": {}, + "source": [ + "## More than three series :)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "070314da", + "metadata": {}, + "outputs": [], + "source": [ + "lens.histogram([[1.0, -20.0, -25.0, -15.0, 2.0, 3.0, 2.0, 2.0, 2.0, 2.0, 4.0, 5.0, 6.0], [1.0, 1.0, 1.0, 1.0, 1.0, 2.0, 3.0, 3.0, 3.0, 3.0, 2.0, 2.0, 2.0],[16.2, 17.9, 18.2, 2.3, -5.2, 6.0, 7.2, -10.9, 8.2,41.0], [1.3, 2.2, 5.3, 7.7, 8.8, 9.0, 2.0, 3.2, 43.7, 4.2, 5.1, 4.5, 6.9], [1.0, 2.1, 3.0, 4.1, 19.0, 6.0, 12.2, 14.9, 15.0, 16.1, 17.2]], 5.0, 'Composite histogram', ['a', 'b', 'c', 'd', 'e'])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2245a600", + "metadata": {}, + "outputs": [], + "source": [ + "# weave-test-skip: in-notebook ops don't yet work with server\n", + "# super simple example for now to illustrate fetching W&B data & plotting histograms\n", + "from wandb.apis import public as wandb_api\n", + "from PIL import Image\n", + "\n", + "@weave.op(render_info={\"type\": \"function\"})\n", + "def run_accuracy_barchart(runs: list[wandb_api.Run]) -> Image.Image:\n", + " runs = list(runs) #[:100] # could shorten this list\n", + " acc = [r.summary_metrics.get(\"acc\") or 0.0 for r in runs]\n", + " car_acc = [r.summary_metrics.get(\"car_acc\") or 0.0 for r in runs]\n", + " traffic_acc = [r.summary_metrics.get(\"traffic_acc\") or 0.0 for r in runs]\n", + " road_acc = [r.summary_metrics.get(\"road_acc\") or 0.0 for r in runs]\n", + " \n", + " # filter out NaNs\n", + " for l in [acc, car_acc, traffic_acc, road_acc]:\n", + " if \"NaN\" in l:\n", + " l = l.remove(\"NaN\")\n", + "\n", + " return weave.use(lens.histogram([acc, car_acc, traffic_acc, road_acc],\n", + " bin_size=0.05,\n", + " chart_title=\"Semantic Segmentation Accuracy Across Experiments by Label Class\", \n", + " series_names=[\"Overall acc\", \"Car acc\", \"Traffic acc\", \"Road acc\"]\n", + " )\n", + " )\n", + "\n", + "runs = weave.legacy.weave.ops.project('stacey', 'deep-drive').runs()\n", + "run_accuracy_barchart(runs)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/weave/legacy/examples/experimental/dir_browsing.ipynb b/weave/legacy/examples/experimental/dir_browsing.ipynb new file mode 100644 index 00000000000..2e5d0e3d569 --- /dev/null +++ b/weave/legacy/examples/experimental/dir_browsing.ipynb @@ -0,0 +1,26 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "79d64dea", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import weave\n", + "\n", + "#weave.legacy.weave.ops.local_path(os.path.abspath('../weave/testdata'))" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/weave/legacy/examples/experimental/ecosystem.ipynb b/weave/legacy/examples/experimental/ecosystem.ipynb new file mode 100644 index 00000000000..2adc1728b23 --- /dev/null +++ b/weave/legacy/examples/experimental/ecosystem.ipynb @@ -0,0 +1,25 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "3d5d0d34", + "metadata": {}, + "outputs": [], + "source": [ + "import weave\n", + "from weave.legacy.weave.ecosystem import ecosystem\n", + "ecosystem()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/weave/legacy/examples/experimental/huggingface_datasets.ipynb b/weave/legacy/examples/experimental/huggingface_datasets.ipynb new file mode 100644 index 00000000000..8f8074494b8 --- /dev/null +++ b/weave/legacy/examples/experimental/huggingface_datasets.ipynb @@ -0,0 +1,89 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "897e0314", + "metadata": {}, + "source": [ + "# HuggingFace Datasets\n", + "\n", + "A quick overview of exploring [Datasets from HuggingFace](https://huggingface.co/docs/datasets/index).\n", + "\n", + "### Known issues\n", + "\n", + "- Using a dataset often reboots the weave server the first time, causing the first request to fail. Run again and it'll work\n", + "- We only load 100 items" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ba78a68c", + "metadata": {}, + "outputs": [], + "source": [ + "import weave\n", + "# Weave package now defaults to eager mode, but lazy mode required for this example notebook for now.\n", + "weave.use_lazy_execution()\n", + "from weave.legacy.weave.ecosystem import huggingface as hf" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3e3b90c5", + "metadata": {}, + "outputs": [], + "source": [ + "hf.hf_datasets()" + ] + }, + { + "cell_type": "markdown", + "id": "d82a6ac3", + "metadata": {}, + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b861a911", + "metadata": {}, + "outputs": [], + "source": [ + "hf.dataset('app_reviews')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9935abb0", + "metadata": {}, + "outputs": [], + "source": [ + "hf.dataset('fashion_mnist')" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/weave/legacy/examples/experimental/huggingface_models.ipynb b/weave/legacy/examples/experimental/huggingface_models.ipynb new file mode 100644 index 00000000000..c28d270f5dd --- /dev/null +++ b/weave/legacy/examples/experimental/huggingface_models.ipynb @@ -0,0 +1,131 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "d46881ea", + "metadata": {}, + "outputs": [], + "source": [ + "import weave\n", + "# Weave package now defaults to eager mode, but lazy mode required for this example notebook for now.\n", + "weave.use_lazy_execution()\n", + "from weave.legacy.weave.ecosystem.huggingface import huggingface as hf" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9d181896", + "metadata": {}, + "outputs": [], + "source": [ + "# Models\n", + "#hf().models()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cb3fb536", + "metadata": {}, + "outputs": [], + "source": [ + "# Model card\n", + "text_gen_model = hf().model('gpt2')\n", + "text_gen_model" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5961a5c5", + "metadata": {}, + "outputs": [], + "source": [ + "# Calling a text generation model\n", + "text_gen_output = text_gen_model.call('This is the start')\n", + "text_gen_output" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "974e1a18", + "metadata": {}, + "outputs": [], + "source": [ + "# Bertviz for attention\n", + "text_gen_output.attention()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b9ab742c", + "metadata": {}, + "outputs": [], + "source": [ + "# Model card\n", + "classification_model = hf().model('bhadresh-savani/distilbert-base-uncased-emotion')\n", + "classification_model" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "493448cc", + "metadata": {}, + "outputs": [], + "source": [ + "classification_output = classification_model.call(\"I'm feeling just swell\")\n", + "classification_output" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "36305bb0", + "metadata": {}, + "outputs": [], + "source": [ + "classification_output.attention()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b1aa95d3", + "metadata": {}, + "outputs": [], + "source": [ + "# Text classification models can be explained with shap\n", + "\n", + "# TODO: we wan't chain shap_explain in Python right now. But we can in JS. Fix!\n", + "from weave.legacy.weave.ecosystem import shap\n", + "shap.shap_explain(classification_output)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/weave/legacy/examples/experimental/image_gen_craiyon.ipynb b/weave/legacy/examples/experimental/image_gen_craiyon.ipynb new file mode 100644 index 00000000000..36eab9de975 --- /dev/null +++ b/weave/legacy/examples/experimental/image_gen_craiyon.ipynb @@ -0,0 +1,52 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "2088ebdf", + "metadata": {}, + "outputs": [], + "source": [ + "# weave-test-skip-all: The craiyon API takes at least 60s per prediction!\n", + "import weave" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b6e6882b", + "metadata": {}, + "outputs": [], + "source": [ + "from weave.legacy.weave.ecosystem import craiyon\n", + "craiyon.generate('hello')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3322e1c5", + "metadata": {}, + "outputs": [], + "source": [ + "prompts = ['cats', 'dogs', 'catdogs']\n", + "panel = weave.legacy.weave.panels.Table(prompts,\n", + " columns=[\n", + " lambda prompt: prompt,\n", + " lambda prompt: craiyon.generate(prompt)\n", + " ]\n", + " )\n", + "weave.show(panel)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/weave/legacy/examples/experimental/image_gen_ops.ipynb b/weave/legacy/examples/experimental/image_gen_ops.ipynb new file mode 100644 index 00000000000..5f3078c5fd2 --- /dev/null +++ b/weave/legacy/examples/experimental/image_gen_ops.ipynb @@ -0,0 +1,126 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "22034460", + "metadata": {}, + "outputs": [], + "source": [ + "import weave\n", + "import io\n", + "from PIL import Image\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "import random\n", + "from weave.legacy.weave import ops\n", + "\n", + "def plt_image():\n", + " \"\"\"Return current matplotlib figure as PIL Image\"\"\"\n", + " img_buf = io.BytesIO()\n", + " plt.savefig(img_buf, format='png')\n", + " im = Image.open(img_buf)\n", + " return im" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d03dc328", + "metadata": {}, + "outputs": [], + "source": [ + "# Declare an op that plots a sin weave using matplotlib, and returns the plot as an Image\n", + "\n", + "@weave.op(render_info={\"type\": \"function\"})\n", + "def sin_image(f: int) -> Image.Image: \n", + " x = np.arange(0, f * np.pi, 0.1)\n", + " y = np.sin(x)\n", + " plt.rcParams[\"figure.figsize\"] = (22, 7)\n", + " plt.plot(x, y)\n", + " \n", + " return plt_image()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "217d0cd2", + "metadata": {}, + "outputs": [], + "source": [ + "x = weave.save(7, name='my-number')\n", + "\n", + "im = sin_image(x + 2)\n", + "im" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d8d06148", + "metadata": {}, + "outputs": [], + "source": [ + "import json\n", + "from weave.legacy.weave.ops_domain import wb_domain_types\n", + "from weave import gql_op_plugin\n", + "import wandb\n", + "\n", + "\n", + "@weave.op(\n", + " render_info={\"type\": \"function\"},\n", + " plugins=gql_op_plugin.wb_gql_op_plugin(\n", + " lambda inputs, inner: \"project {id name entity {id name}}\"\n", + " )\n", + ")\n", + "def run_accuracy_barchart(runs: list[wb_domain_types.Run]) -> Image.Image:\n", + " runs = list(runs)[:10]\n", + " runs = [wandb.Api().run(f'{r[\"project\"][\"entity\"][\"name\"]}/{r[\"project\"][\"name\"]}/{r[\"name\"]}') for r in runs]\n", + " names = [r.name for r in runs]\n", + " xs = range(len(runs))\n", + " ys = [r.summary_metrics.get(\"acc\") or 0 for r in runs]\n", + "\n", + " plt.rcParams[\"figure.figsize\"] = (15, 5)\n", + " plt.bar(xs, ys, align=\"center\", alpha=0.5)\n", + " plt.xticks(xs, names)\n", + " plt.ylabel(\"acc\")\n", + " plt.title(\"Run accuracies\")\n", + "\n", + " return plt_image()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a4dc62a2", + "metadata": {}, + "outputs": [], + "source": [ + "runs = weave.legacy.weave.ops.project('shawn', 'fasion-sweep').runs()\n", + "run_accuracy_barchart(runs)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/weave/legacy/examples/experimental/layout_panels.ipynb b/weave/legacy/examples/experimental/layout_panels.ipynb new file mode 100644 index 00000000000..c7bd996af26 --- /dev/null +++ b/weave/legacy/examples/experimental/layout_panels.ipynb @@ -0,0 +1,206 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "cfebbf95", + "metadata": {}, + "outputs": [], + "source": [ + "import weave\n", + "from weave.legacy.weave import panels" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e427d65a", + "metadata": {}, + "outputs": [], + "source": [ + "# LabeledItem example\n", + "# panels.LabeledItem(item='item', label='label')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f9852987", + "metadata": {}, + "outputs": [], + "source": [ + "# Group example\n", + "# panels.Group(\n", + "# items=[\n", + "# panels.Group(\n", + "# prefer_horizontal=True,\n", + "# items=[\n", + "# panels.LabeledItem(item='item1', label='label1'),\n", + "# panels.LabeledItem(item='item2', label='label2'),\n", + "# panels.LabeledItem(item='item3', label='label3'),\n", + "# ]\n", + "# ),\n", + "# panels.LabeledItem(item='item4', label='label4'),\n", + "# panels.LabeledItem(item='item5', label='label5')\n", + "# ]\n", + "# )" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6ee2951b", + "metadata": {}, + "outputs": [], + "source": [ + "# Card example\n", + "\n", + "panel = panels.Card(\n", + " title='GPT-3',\n", + " subtitle='OpenAI',\n", + " content=[\n", + " panels.CardTab(\n", + " name='Overview',\n", + " content=panels.Group(\n", + " items={\n", + " '0': panels.Group(\n", + " preferHorizontal=True,\n", + " items={\n", + " 'a': panels.LabeledItem(item='item1', label='label1'),\n", + " 'b': panels.LabeledItem(item='item2', label='label2'),\n", + " 'c': panels.LabeledItem(item='item3', label='label3'),\n", + " }\n", + " ),\n", + " '1': panels.LabeledItem(item='item4', label='label4'),\n", + " '2': panels.LabeledItem(item='item5', label='label5')\n", + " }\n", + " )\n", + " ),\n", + " panels.CardTab(\n", + " name='Limitations & Use',\n", + " content=panels.LabeledItem(item='tab2', label='tab2-label')\n", + " ),\n", + " \n", + " ]\n", + ")\n", + "panel" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "762467e8", + "metadata": {}, + "outputs": [], + "source": [ + "# Table in Card example\n", + "\n", + "# panels.Card(\n", + "# title='GPT-3',\n", + "# subtitle='OpenAI',\n", + "# content=[\n", + "# panels.CardTab(\n", + "# name='Overview',\n", + "# content=panels.Group(\n", + "# items=[\n", + "# panels.Group(\n", + "# prefer_horizontal=True,\n", + "# items=[\n", + "# panels.LabeledItem(item='item1', label='label1'),\n", + "# panels.LabeledItem(item='item2', label='label2'),\n", + "# panels.LabeledItem(item='item3', label='label3'),\n", + "# ]\n", + "# ),\n", + "# panels.LabeledItem(item='item4', label='label4'),\n", + "# panels.LabeledItem(\n", + "# item=[\n", + "# {\"a\": 5, \"b\": 6},\n", + "# {\"a\": 14, \"b\": 9},\n", + "# {\"a\": 4, \"b\": 22},\n", + "# ],\n", + "# height=400,\n", + "# label=\"Example\",\n", + "# ),\n", + "# ]\n", + "# )\n", + "# ),\n", + "# panels.CardTab(\n", + "# name='Limitations & Use',\n", + "# content=panels.LabeledItem(item='tab2', label='tab2-label')\n", + "# ),\n", + " \n", + "# ]\n", + "# )" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "81c83f19", + "metadata": {}, + "outputs": [], + "source": [ + "# If you want full control over table columns\n", + "\n", + "# table = panels.Table([\n", + "# {\"a\": 5, \"b\": 6},\n", + "# {\"a\": 14, \"b\": 9},\n", + "# {\"a\": 4, \"b\": 22},\n", + "# ])\n", + "# table.append_column(lambda row: row['a'] + 3, name=\"a name\")\n", + "# table.append_column(lambda row: row['a'] + row['b']) # Will have an automatic name\n", + "\n", + "# panels.LabeledItem(\n", + "# item=table,\n", + "# height=400,\n", + "# label=\"Example\",\n", + "# )" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "eaea1261", + "metadata": {}, + "outputs": [], + "source": [ + "# Plot in Labeled item (you can do the same in a Card)\n", + "\n", + "# plot = panels.Plot(\n", + "# input_node=[\n", + "# {\"a\": 5, \"b\": 6},\n", + "# {\"a\": 14, \"b\": 9},\n", + "# {\"a\": 4, \"b\": 22}],\n", + "# x=lambda row: row['a'],\n", + "# y=lambda row: row['b']\n", + "# )\n", + "\n", + "# panels.LabeledItem(\n", + "# item=plot,\n", + "# label=\"Example\",\n", + "# )" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/weave/legacy/examples/experimental/llm_monitor_helper.ipynb b/weave/legacy/examples/experimental/llm_monitor_helper.ipynb new file mode 100644 index 00000000000..48e92a18e90 --- /dev/null +++ b/weave/legacy/examples/experimental/llm_monitor_helper.ipynb @@ -0,0 +1,198 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ENTITY = 'timssweeney'\n", + "PROJECT = 'weave'\n", + "STREAM = 'custom_llm_monitoring_example'" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import uuid\n", + "import time\n", + "import typing\n", + "from datetime import datetime \n", + "\n", + "from weave.stream_data_interfaces import LLMCompletionDict, _LLMCompletionInputs, _LLMCompletionOutput, _LLMCompletionSummary, _LLMCompletionMessage, _LLMCompletionChoice\n", + "import tiktoken\n", + "\n", + "def count_chat_completion_tokens(\n", + " model_name: typing.Optional[str] = None,\n", + " prompt_input_messages: typing.List[_LLMCompletionMessage] = [],\n", + " completion_choices: typing.List[_LLMCompletionChoice] = []\n", + ") -> dict:\n", + " \n", + " summary = {}\n", + " if not model_name:\n", + " encoding = tiktoken.get_encoding(\"cl100k_base\")\n", + " else:\n", + " encoding = tiktoken.encoding_for_model(model_name)\n", + "\n", + " prompt_tokens = (encoding.encode(m[\"content\"]) for m in prompt_input_messages)\n", + " summary[\"prompt_tokens\"] = sum(len(c) for c in prompt_tokens)\n", + "\n", + " completion_tokens = (\n", + " encoding.encode(c[\"message\"][\"content\"]) for c in completion_choices\n", + " )\n", + " summary[\"completion_tokens\"] = sum(len(c) for c in completion_tokens)\n", + " summary[\"total_tokens\"] = summary[\"prompt_tokens\"] + summary[\"completion_tokens\"]\n", + " return summary\n", + "\n", + "def create_llm_record(\n", + "\n", + " # The end-format is that of `_LLMCompletionInputs`, but we can add helper processing to convert from other formats\n", + " inputs: typing.Union[_LLMCompletionInputs, typing.List[_LLMCompletionMessage], typing.List[str], str] = None,\n", + "\n", + " # the end-format is that of `_LLMCompletionOutput`, but we can add helper processing to convert from other formats\n", + " output: typing.Union[_LLMCompletionOutput, str] = None,\n", + "\n", + " # Required for cost analysis\n", + " model_name: str = \"\",\n", + "\n", + " # Optional fields\n", + " span_id: typing.Optional[str] = None,\n", + " name: typing.Optional[str] = None,\n", + " trace_id: typing.Optional[str] = None,\n", + " status_code: typing.Optional[str] = None,\n", + " start_time_s: typing.Optional[float] = None,\n", + " end_time_s: typing.Optional[float] = None,\n", + " parent_id: typing.Optional[str] = None,\n", + "\n", + " # Must contain dictionaries, lists, and primitives (ie json serializable)\n", + " attributes: typing.Optional[typing.Dict[str, typing.Any]] = None,\n", + "\n", + " summary: typing.Optional[_LLMCompletionSummary] = None,\n", + "\n", + " exception: typing.Optional[str] = None\n", + "):\n", + " span_id = span_id or str(uuid.uuid4())\n", + " name = name or \"llm_completion\"\n", + " trace_id = trace_id or span_id\n", + " status_code = status_code or \"UNSET\"\n", + " start_time_s = start_time_s or time.time()\n", + " end_time_s = end_time_s or (start_time_s + 1)\n", + " latency_s = end_time_s - start_time_s\n", + " # parent_id can be None\n", + " attributes = attributes or {}\n", + " \n", + "\n", + " # Input handling\n", + " if isinstance(inputs, dict):\n", + " # Assume correct format\n", + " inputs = inputs\n", + " elif isinstance(inputs, list):\n", + " messages = []\n", + " for item in inputs:\n", + " if isinstance(item, str):\n", + " messages.append(_LLMCompletionMessage(content=item))\n", + " elif isinstance(item, dict):\n", + " # Assume correct format\n", + " messages.append(item)\n", + " else:\n", + " raise ValueError(f\"Invalid type for item in inputs: {type(item)}\")\n", + " inputs = _LLMCompletionInputs(messages=messages)\n", + " elif isinstance(inputs, str):\n", + " inputs = _LLMCompletionInputs(messages=[_LLMCompletionMessage(content=inputs)])\n", + " else:\n", + " raise ValueError(f\"Invalid type for inputs: {type(inputs)}\")\n", + " \n", + "\n", + " # Output handling\n", + " if isinstance(output, dict):\n", + " # Assume correct format\n", + " output = output\n", + " elif isinstance(output, str):\n", + " output = _LLMCompletionOutput(model=model_name, choices=[_LLMCompletionChoice(message=_LLMCompletionMessage(content=output))])\n", + " else:\n", + " raise ValueError(f\"Invalid type for output: {type(output)}\")\n", + "\n", + " # Sort of odd, but we need at least one key for now\n", + " summary = summary or {}\n", + " summary = {\n", + " **(summary or {}),\n", + " \"latency_s\": latency_s,\n", + " **count_chat_completion_tokens(\n", + " model_name,\n", + " inputs['messages'],\n", + " output['choices'])\n", + " }\n", + " \n", + "\n", + " # exception can be None\n", + "\n", + " assert status_code in [\"SUCCESS\", \"ERROR\", \"UNSET\"]\n", + "\n", + " return LLMCompletionDict(\n", + " span_id = span_id,\n", + " name = name,\n", + " trace_id = trace_id,\n", + " status_code = status_code,\n", + " start_time_s = start_time_s,\n", + " end_time_s = end_time_s,\n", + " parent_id = parent_id,\n", + " attributes = attributes,\n", + " inputs = inputs,\n", + " output = output,\n", + " summary = summary,\n", + " exception = exception,\n", + " # Manually set timestamp - else it will be set to the time of the function call\n", + " timestamp = datetime.fromtimestamp(start_time_s)\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from weave.legacy.weave.monitoring import StreamTable\n", + "st = StreamTable(f\"{ENTITY}/{PROJECT}/{STREAM}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Base Case\n", + "record = create_llm_record(\n", + " inputs=\"hello\",\n", + " output=\"world\"\n", + ")\n", + "st.log(record)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "wandb-weave", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.8" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/weave/legacy/examples/experimental/mnist_train.ipynb b/weave/legacy/examples/experimental/mnist_train.ipynb new file mode 100644 index 00000000000..664459dcae5 --- /dev/null +++ b/weave/legacy/examples/experimental/mnist_train.ipynb @@ -0,0 +1,82 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "37f6d676", + "metadata": {}, + "outputs": [], + "source": [ + "import weave\n", + "# Weave package now defaults to eager mode, but lazy mode required for this example notebook for now.\n", + "weave.use_lazy_execution()\n", + "from weave.legacy.weave.ecosystem import torchvision, torch_mnist_model_example" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b450f4df", + "metadata": {}, + "outputs": [], + "source": [ + "# TODO: Fix WeaveJS to automatically pick the more specific dataset_card Panel\n", + "mnist_dataset = torchvision.mnist(100) # Or try mnist.food101(100), but currently broken\n", + "mnist_dataset" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "664f3447", + "metadata": {}, + "outputs": [], + "source": [ + "hyperparams = {\n", + " 'fc_layer_size': 256,\n", + " 'dropout': 0.5,\n", + " 'epochs': 5,\n", + " 'learning_rate': 0.005,\n", + " 'batch_size': 128,\n", + "}\n", + "train_split = mnist_dataset['data']['train']\n", + "#train_split.pick('image')\n", + "model = torch_mnist_model_example.train(\n", + " train_split.pick('image'), train_split.pick('label'), hyperparams)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8d204664", + "metadata": {}, + "outputs": [], + "source": [ + "test_split_images = mnist_dataset['data']['test']['image']\n", + "preds = model.predict(test_split_images)\n", + "preds" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/weave/legacy/examples/experimental/object_version_compare.ipynb b/weave/legacy/examples/experimental/object_version_compare.ipynb new file mode 100644 index 00000000000..216aba6973b --- /dev/null +++ b/weave/legacy/examples/experimental/object_version_compare.ipynb @@ -0,0 +1,116 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "e114a840", + "metadata": {}, + "outputs": [], + "source": [ + "import math\n", + "import weave\n", + "import typing" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "47ae117d", + "metadata": {}, + "outputs": [], + "source": [ + "@weave.op(render_info={'type': 'function'})\n", + "def weave_range(n: int) -> list[int]:\n", + " return list(range(n))\n", + "\n", + "class Point(typing.TypedDict):\n", + " x: float\n", + " y: float\n", + "\n", + "@weave.op()\n", + "def compute_points(xs: list[int], freq: float) -> list[Point]:\n", + " res: list[Point] = []\n", + " for x in xs:\n", + " res.append({\"x\": x, \"y\": math.sin(freq * x)})\n", + " return res" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e6db91c9", + "metadata": {}, + "outputs": [], + "source": [ + "xs = weave_range(1000)\n", + "points = compute_points(xs, 0.16)\n", + "points = weave.save(points, 'points')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5c88c35e", + "metadata": {}, + "outputs": [], + "source": [ + "plot = weave.legacy.weave.panels.Plot(points,\n", + " x=lambda row: row['x'],\n", + " y=lambda row: row['y'])\n", + "plot\n", + "\n", + "# NOTE! There is a UI bug here. You need to click the gear icon and\n", + "# then \"Reset & Automate Plot\" -> \"OK\" to make it render correctly" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "db404b2e", + "metadata": {}, + "outputs": [], + "source": [ + "for version in weave.versions(points):\n", + " print(weave.expr(version))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "caa2daef", + "metadata": {}, + "outputs": [], + "source": [ + "plot = weave.legacy.weave.panels.Plot(weave.legacy.weave.ops.compare_versions(points))\n", + "plot.set_x(lambda row: row['x'])\n", + "plot.set_y(lambda row: row['y'])\n", + "plot.set_label(lambda row: row['version'])\n", + "weave.show(plot)\n", + "\n", + "# NOTE! There is a UI bug here. You need to click the gear icon and\n", + "# then \"Reset & Automate Plot\" -> \"OK\" to make it render correctly" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/weave/legacy/examples/experimental/old_openai_monitoring.ipynb b/weave/legacy/examples/experimental/old_openai_monitoring.ipynb new file mode 100644 index 00000000000..038a1932e79 --- /dev/null +++ b/weave/legacy/examples/experimental/old_openai_monitoring.ipynb @@ -0,0 +1,226 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "30ccfdbc", + "metadata": {}, + "source": [ + "This notebook shows how to use our openai logging integration to monitor api calls" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8107732a-fb90-45f8-8377-6381bd28475d", + "metadata": {}, + "outputs": [], + "source": [ + "import weave\n", + "weave.use_frontend_devmode()\n", + "from weave.legacy.weave.monitoring import openai, init_monitor\n", + "\n", + "OPENAI_MODEL = 'gpt-3.5-turbo'" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ba2b2070", + "metadata": {}, + "outputs": [], + "source": [ + "# Pass //\n", + "m = init_monitor('shawn/oai-mon/test21')\n", + "\n", + "# Do an initial request, otherwise we don't have a type on which to recommend the OpenAI board!\n", + "# We need at least 2 requests for the Board to work, otherwise we get divide by zero errors.\n", + "# TODO: fix this onboarding issue\n", + "r = openai.ChatCompletion.create(model=OPENAI_MODEL, messages=[{\"role\": \"user\", \"content\": f\"hello world!\"}])\n", + "r = openai.ChatCompletion.create(model=OPENAI_MODEL, messages=[{\"role\": \"user\", \"content\": f\"what is 2+2?\"}])" + ] + }, + { + "cell_type": "markdown", + "id": "fb51abb4", + "metadata": {}, + "source": [ + "Click the link above to go to the Weave UI for the table we're logging to.\n", + "\n", + "From there you can click \"OpenAI Monitor Board\" to create a Weave Board for this data stream." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e711d521", + "metadata": {}, + "outputs": [], + "source": [ + "# Monitor ChatCompletion requests\n", + "r = openai.ChatCompletion.create(model=OPENAI_MODEL, messages=[\n", + " {\"role\": \"user\", \"content\": f\"who won the world series in 2006?\"},\n", + " ])\n", + "r['choices'][0]['message']['content']" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7930e774", + "metadata": {}, + "outputs": [], + "source": [ + "# To keep track of prompts and parameters, add them to attributes on the logged\n", + "# record.\n", + "\n", + "system_prompt = \"you always write in bullet points\"\n", + "prompt = 'solve the following equation step by step: {equation}'\n", + "params = {'equation': '4 * (3 - 1)'}\n", + "openai.ChatCompletion.create(model=OPENAI_MODEL,\n", + " messages=[\n", + " {\"role\": \"system\", \"content\": system_prompt},\n", + " {\"role\": \"user\", \"content\": prompt.format(**params)},\n", + " ],\n", + " # you can add additional attributes to the logged record\n", + " # see the monitor_api notebook for more examples\n", + " monitor_attributes={\n", + " 'system_prompt': system_prompt,\n", + " 'prompt': prompt,\n", + " 'params': params\n", + " })" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b8b423df", + "metadata": {}, + "outputs": [], + "source": [ + "# Monitor streaming requests\n", + "# TODO: we don't get token counts here yet.\n", + "from weave.legacy.weave.monitoring.openai import message_from_stream\n", + "r = openai.ChatCompletion.create(model=OPENAI_MODEL, messages=[\n", + " {\"role\": \"system\", \"content\": \"Your are a robot and only speak in robot, like beep bloop bop.\"},\n", + " {\"role\": \"user\", \"content\": f\"Tell me a 50 word story.\"},\n", + " ], stream=True)\n", + "for s in message_from_stream(r):\n", + " print(s, end='')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bca62fdb", + "metadata": {}, + "outputs": [], + "source": [ + "# Render table inline\n", + "#m.rows()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bdae9ba5-aa2b-4191-9a42-c1078ba0698a", + "metadata": {}, + "outputs": [], + "source": [ + "# Render board inline\n", + "# from weave.legacy.weave.panels_py import panel_llm_monitor\n", + "# board = panel_llm_monitor.board.raw_resolve_fn(m.rows())\n", + "# board" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d237a885", + "metadata": {}, + "outputs": [], + "source": [ + "# Other examples\n", + "\n", + "# TODO: restore async variant\n", + "\n", + "# result = await monitored_a_create(model=\"gpt-3.5-turbo\", messages=[\n", + "# {\"role\": \"system\", \"content\": \"You are a world-class machine learning researcher.\"},\n", + "# {\"role\": \"user\", \"content\": f\"Please provide a simple, fact-based question to send to an AI system. Do not say anything other than the question itself. Use this random number as inspiration: {random.random()}.\"},\n", + "# ])\n", + "# result\n", + "\n", + "# TODO: ensure works with function calls\n", + "# functions = [\n", + "# {\n", + "# \"name\": \"get_current_weather\",\n", + "# \"description\": \"Get the current weather\",\n", + "# \"parameters\": {\n", + "# \"type\": \"object\",\n", + "# \"properties\": {\n", + "# \"location\": {\n", + "# \"type\": \"string\",\n", + "# \"description\": \"The city and state, e.g. San Francisco, CA\",\n", + "# },\n", + "# \"format\": {\n", + "# \"type\": \"string\",\n", + "# \"enum\": [\"celsius\", \"fahrenheit\"],\n", + "# \"description\": \"The temperature unit to use. Infer this from the users location.\",\n", + "# },\n", + "# },\n", + "# \"required\": [\"location\", \"format\"],\n", + "# },\n", + "# },\n", + "# {\n", + "# \"name\": \"get_n_day_weather_forecast\",\n", + "# \"description\": \"Get an N-day weather forecast\",\n", + "# \"parameters\": {\n", + "# \"type\": \"object\",\n", + "# \"properties\": {\n", + "# \"location\": {\n", + "# \"type\": \"string\",\n", + "# \"description\": \"The city and state, e.g. San Francisco, CA\",\n", + "# },\n", + "# \"format\": {\n", + "# \"type\": \"string\",\n", + "# \"enum\": [\"celsius\", \"fahrenheit\"],\n", + "# \"description\": \"The temperature unit to use. Infer this from the users location.\",\n", + "# },\n", + "# \"num_days\": {\n", + "# \"type\": \"integer\",\n", + "# \"description\": \"The number of days to forecast\",\n", + "# }\n", + "# },\n", + "# \"required\": [\"location\", \"format\", \"num_days\"]\n", + "# },\n", + "# },\n", + "# ]\n", + "\n", + "# result = openai.ChatCompletion.create(model=\"gpt-3.5-turbo\", functions=functions, messages=[\n", + "# {\"role\": \"system\", \"content\": \"You love to call functions.\"},\n", + "# {\"role\": \"user\", \"content\": f\"what's the weather today\"},\n", + "# ])" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/weave/legacy/examples/experimental/openai_monitoring.ipynb b/weave/legacy/examples/experimental/openai_monitoring.ipynb new file mode 100644 index 00000000000..686b881dc0d --- /dev/null +++ b/weave/legacy/examples/experimental/openai_monitoring.ipynb @@ -0,0 +1,239 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## OpenAI Monitoring Examples\n", + "\n", + "This updates our monitoring code to:\n", + "1. Be compatible with `openai>=1.0.0`\n", + "2. Use patching (instead of forcing the user to import from our custom impl)\n", + "3. Use a callback system to add new functionality before/after certain stages" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Setup" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import openai\n", + "import weave\n", + "from weave.legacy.weave.monitoring.openai import patch, unpatch\n", + "\n", + "weave.legacy.weave.monitoring.openai.patch()\n", + "# weave.legacy.weave.monitoring.openai.unpatch()\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Advanced Setup\n", + "You can customize callbacks (e.g. which StreamTable to log to) by explicitly passing callbacks to `patch`" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# from weave.legacy.weave.monitoring.openai import ReassembleStream, LogToStreamTable\n", + "\n", + "# weave.legacy.weave.monitoring.openai.patch(\n", + "# callbacks=[LogToStreamTable.from_stream_name(\"stream\", \"project\", \"entity\")]\n", + "# )\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Module-level Sync Completion" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "result = openai.chat.completions.create(\n", + " model=\"gpt-3.5-turbo\",\n", + " messages=[{\"role\": \"system\", \"content\": \"Tell me a joke\"}],\n", + ")\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "result\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Sync Completion" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "client = openai.OpenAI()\n", + "\n", + "result = client.chat.completions.create(\n", + " model=\"gpt-3.5-turbo\",\n", + " messages=[{\"role\": \"system\", \"content\": \"Tell me a joke\"}],\n", + ")\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "result\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Sync Completion (Streaming)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "client = openai.OpenAI()\n", + "\n", + "stream = client.chat.completions.create(\n", + " model=\"gpt-3.5-turbo\",\n", + " messages=[{\"role\": \"system\", \"content\": \"Tell me a joke\"}],\n", + " stream=True\n", + ")\n", + "\n", + "for x in stream:\n", + " print(x)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Async Completion" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "client = openai.AsyncOpenAI()\n", + "\n", + "result = await client.chat.completions.create(\n", + " model=\"gpt-3.5-turbo\",\n", + " messages=[{\"role\": \"system\", \"content\": \"Tell me a joke\"}],\n", + ")\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "result\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Async Completion (Streaming)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "client = openai.AsyncOpenAI()\n", + "\n", + "stream = await client.chat.completions.create(\n", + " model=\"gpt-3.5-turbo\",\n", + " messages=[{\"role\": \"system\", \"content\": \"Tell me a joke\"}],\n", + " stream=True\n", + ")\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "async for x in stream:\n", + " ...\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "weave310", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.11" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/weave/legacy/examples/experimental/prompts_dev/generate_synth_mon_board.ipynb b/weave/legacy/examples/experimental/prompts_dev/generate_synth_mon_board.ipynb new file mode 100644 index 00000000000..2ee6b8f6d03 --- /dev/null +++ b/weave/legacy/examples/experimental/prompts_dev/generate_synth_mon_board.ipynb @@ -0,0 +1,192 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "4549831c", + "metadata": {}, + "source": [ + "\"Weights\n", + "\n", + "\n", + "\n", + " \"Open\n", + "\n", + "\n", + "# Generate Synthetic Data for Large-Scale LLM Monitoring Demo\n", + "\n", + "This notebook generates synthetic data (Shakespearean dialogue) in the format used by the monitoring.openai integration.\n", + "See the openai_monitoring notebook for instructions for logging real OpenAI API calls.\n", + "\n", + "# Step 0: Setup\n", + "\n", + "Import dependencies" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "00647082", + "metadata": {}, + "outputs": [], + "source": [ + "!pip install -qqq weave\n", + "\n", + "import uuid\n", + "from datetime import timedelta\n", + "import weave\n", + "from weave.legacy.weave import ops_arrow\n", + "from weave.legacy.weave.monitoring import monitor\n", + "from weave.syndata_mon import random_predictions" + ] + }, + { + "cell_type": "markdown", + "id": "a8b49310-4d7a-4f3f-a901-e1f9e87baff8", + "metadata": {}, + "source": [ + "# Step 1: Generate random predictions and montior call spans" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "93653c45", + "metadata": {}, + "outputs": [], + "source": [ + "preds = random_predictions(100)\n", + "\n", + "# Convert synthetic data into the format used by the weave.legacy.weave.monitoring.openai integration\n", + "\n", + "# convert model_version in the synthetic data to an openai model version\n", + "# this makes it so that there's a new API key that has appeared in our logs recently, and that key\n", + "# has started using gpt-4 which makes a cost spike\n", + "versions = sorted(preds.column('model_version').unique())\n", + "version_map = {}\n", + "for i, v in enumerate(reversed(versions)):\n", + " api_key = 'sk-U4...yK7z'\n", + " model = 'gpt-3.5-turbo-0613'\n", + " if i == 1 or i == 2:\n", + " # second and third most recent versions use a different api key\n", + " api_key = 'sk-U9...a22c'\n", + " if i == 1:\n", + " # second most recent version uses gpt-4\n", + " model = 'gpt-4-0613'\n", + " version_map[v] = (api_key, model)\n", + " \n", + "spans = [] \n", + "for i, pred in enumerate(preds):\n", + " api_key, model = version_map[pred['model_version']]\n", + " latency_mult = 1\n", + " if model == 'gpt-4-0613':\n", + " latency_mult = 3\n", + " span = monitor.Span('openai.api_resources.chat_completion.type.create',\n", + " inputs={\n", + " 'messages':[\n", + " {\"role\": \"user\", \"content\": pred['prompt']}\n", + " ]\n", + " },\n", + " output={\n", + " 'id': 'chatcmpl-%s' % uuid.uuid4(),\n", + " 'object': 'chat.completion',\n", + " 'created': pred['timestamp'].timestamp(),\n", + " 'model': model,\n", + " 'choices': [\n", + " {\n", + " 'index': 0,\n", + " 'message': {\n", + " 'role': 'assistant',\n", + " 'content': pred['completion']\n", + " },\n", + " 'finish_reason': 'stop'\n", + " }\n", + " ],\n", + "\n", + " },\n", + " attributes={\n", + " 'api_key': api_key,\n", + " 'username': pred['username']\n", + " },\n", + " summary={\n", + " 'prompt_tokens': pred['prompt_tokens'],\n", + " 'completion_tokens': pred['completion_tokens'],\n", + " 'total_tokens': (pred['prompt_tokens'] + pred['completion_tokens'])\n", + " })\n", + " span.start_time = pred['timestamp']\n", + " span.end_time = pred['timestamp'] + timedelta(seconds=pred['latency'] * latency_mult)\n", + " spans.append({'timestamp': pred['timestamp'], **span.asdict()})" + ] + }, + { + "cell_type": "markdown", + "id": "88479dbd-6fae-4353-974e-5e13704f720f", + "metadata": {}, + "source": [ + "# Step 2: Save and view synthetic data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9548c9aa", + "metadata": {}, + "outputs": [], + "source": [ + "# Save as a local table\n", + "oai_data = weave.save(ops_arrow.to_arrow(spans), 'oai_data')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b59be0b5", + "metadata": {}, + "outputs": [], + "source": [ + "oai_data" + ] + }, + { + "cell_type": "markdown", + "id": "46669b4f-c569-40ee-a6fe-c5f2fdf64eb7", + "metadata": {}, + "source": [ + "# Step 3: View LLM Monitoring Board with synthetic data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7045f1eb", + "metadata": {}, + "outputs": [], + "source": [ + "# Use the llm monitoring template to visualize the data\n", + "from weave.legacy.weave.panels_py import panel_llm_monitor\n", + "board = panel_llm_monitor.board.raw_resolve_fn(oai_data)\n", + "board" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/weave/legacy/examples/experimental/prompts_dev/synthetic_openai_data.ipynb b/weave/legacy/examples/experimental/prompts_dev/synthetic_openai_data.ipynb new file mode 100644 index 00000000000..6baaae82cfb --- /dev/null +++ b/weave/legacy/examples/experimental/prompts_dev/synthetic_openai_data.ipynb @@ -0,0 +1,167 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "4549831c", + "metadata": {}, + "source": [ + "\"Weights\n", + "\n", + "# Generate Synthetic Trace Data\n", + "\n", + "Generate some synthetic OpenAI data we can explore in a LLM Monitoring Debugging Board." + ] + }, + { + "cell_type": "markdown", + "id": "e3d9305e", + "metadata": {}, + "source": [ + "# Step 0: Setup" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "00647082", + "metadata": {}, + "outputs": [], + "source": [ + "import uuid\n", + "from datetime import timedelta\n", + "import weave\n", + "\n", + "from weave.legacy.weave import ops_arrow\n", + "from weave.legacy.weave.monitoring import monitor, StreamTable" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "93041e2e", + "metadata": {}, + "outputs": [], + "source": [ + "from weave.syndata_mon import random_predictions" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "61b9c12a", + "metadata": {}, + "outputs": [], + "source": [ + "WB_ENTITY = # replace with your W&B username or team name\n", + "WB_PROJECT = \"weave\"\n", + "WB_STREAM = \"synthetic_openai_stream\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "93653c45", + "metadata": {}, + "outputs": [], + "source": [ + "preds = random_predictions(10)\n", + "\n", + "# Convert synthetic data into the format used by the weave.legacy.weave.monitoring.openai integration\n", + "\n", + "# convert model_version in the synthetic data to an openai model version\n", + "# this makes it so that there's a new API key that has appeared in our logs recently, and that key\n", + "# has started using gpt-4 which makes a cost spike\n", + "versions = sorted(preds.column('model_version').unique())\n", + "version_map = {}\n", + "for i, v in enumerate(reversed(versions)):\n", + " api_key = 'sk-U4...yK7z'\n", + " model = 'gpt-3.5-turbo-0613'\n", + " if i == 1 or i == 2:\n", + " # second and third most recent versions use a different api key\n", + " api_key = 'sk-U9...a22c'\n", + " if i == 1:\n", + " # second most recent version uses gpt-4\n", + " model = 'gpt-4-0613'\n", + " version_map[v] = (api_key, model)\n", + " \n", + "spans = [] \n", + "for i, pred in enumerate(preds):\n", + " api_key, model = version_map[pred['model_version']]\n", + " latency_mult = 1\n", + " if model == 'gpt-4-0613':\n", + " latency_mult = 3\n", + " span = monitor.Span('openai.api_resources.chat_completion.type.create',\n", + " inputs={\n", + " 'messages':[\n", + " {\"role\": \"user\", \"content\": pred['prompt']}\n", + " ]\n", + " },\n", + " output={\n", + " 'id': 'chatcmpl-%s' % uuid.uuid4(),\n", + " 'object': 'chat.completion',\n", + " 'created': pred['timestamp'].timestamp(),\n", + " 'model': model,\n", + " 'choices': [\n", + " {\n", + " 'index': 0,\n", + " 'message': {\n", + " 'role': 'assistant',\n", + " 'content': pred['completion']\n", + " },\n", + " 'finish_reason': 'stop'\n", + " }\n", + " ],\n", + "\n", + " },\n", + " attributes={\n", + " 'api_key': api_key,\n", + " 'username': pred['username']\n", + " },\n", + " summary={\n", + " 'prompt_tokens': pred['prompt_tokens'],\n", + " 'completion_tokens': pred['completion_tokens'],\n", + " 'total_tokens': (pred['prompt_tokens'] + pred['completion_tokens'])\n", + " })\n", + " span.start_time = pred['timestamp']\n", + " span.end_time = pred['timestamp'] + timedelta(seconds=pred['latency'] * latency_mult)\n", + " spans.append({'timestamp': pred['timestamp'], **span.asdict()})" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9548c9aa", + "metadata": {}, + "outputs": [], + "source": [ + "st = StreamTable(f\"{WB_ENTITY}/{WB_PROJECT}/{WB_STREAM}\")\n", + "\n", + "for span in spans:\n", + " st.log(span)\n", + "\n", + "st.finish()\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/experimental/skip_test/00 Tutorial - Data versioning.ipynb b/weave/legacy/examples/experimental/skip_test/00 Tutorial - Data versioning.ipynb similarity index 100% rename from examples/experimental/skip_test/00 Tutorial - Data versioning.ipynb rename to weave/legacy/examples/experimental/skip_test/00 Tutorial - Data versioning.ipynb diff --git a/weave/legacy/examples/experimental/skip_test/04 Tutorial - Publishing Data and Ops.ipynb b/weave/legacy/examples/experimental/skip_test/04 Tutorial - Publishing Data and Ops.ipynb new file mode 100644 index 00000000000..e7355e75f3f --- /dev/null +++ b/weave/legacy/examples/experimental/skip_test/04 Tutorial - Publishing Data and Ops.ipynb @@ -0,0 +1,125 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "b174bac3", + "metadata": {}, + "outputs": [], + "source": [ + "import weave\n", + "from typing import Any\n", + "\n", + "@weave.op()\n", + "def inc(a: int, delta: int = 1) -> int:\n", + " return a + delta\n", + "\n", + "# store x in local dir\n", + "x = weave.save(10, \"local-num\")\n", + "print(x)\n", + "# store y as a remote artifact - this is stateful and will create\n", + "# new versions if you've previously saved this artifact\n", + "y = weave.publish(100, \"remote-num\")\n", + "print(y)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1949651f", + "metadata": {}, + "outputs": [], + "source": [ + "# increment constant\n", + "print(weave.use(inc(1)))\n", + "# increment local number - DO NOT SAVE\n", + "print(weave.use(inc(x)))\n", + "# increment remote number - DO NOT SAVE\n", + "print(weave.use(inc(y)))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "efd9ed1c", + "metadata": {}, + "outputs": [], + "source": [ + "# this might take a minute since constructing w&b artifacts is a bit slow atm\n", + "for i in range(0, 5):\n", + " y = weave.use(inc(y, 1))\n", + " # publish result back to remote-num\n", + " weave.publish(y, \"remote-num\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "35a0e593", + "metadata": {}, + "outputs": [], + "source": [ + "# TODO: this doesn't work yet, it only returns the current version\n", + "for version in weave.versions(y):\n", + " print(version)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9d6a69fc", + "metadata": {}, + "outputs": [], + "source": [ + "# we can also publish ops\n", + "remote_op = weave.publish(inc)\n", + "print(remote_op)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7548b05f", + "metadata": {}, + "outputs": [], + "source": [ + "# this cell can be run from a full kernel restart and it should work\n", + "import weave\n", + "import shutil\n", + "from weave.legacy.weave.artifacts_local import LOCAL_ARTIFACT_DIR\n", + "from weave import uris\n", + "\n", + "try:\n", + " shutil.rmtree(LOCAL_ARTIFACT_DIR)\n", + "except FileNotFoundError:\n", + " pass\n", + "\n", + "remote_y = uris.WeaveURI.parse('wandb-artifact://jzhao/weave_ops/remote-num:v0').to_ref().get()\n", + "remote_inc = uris.WeaveURI.parse('wandb-artifact://jzhao/weave_ops/op-def-op-inc:v0').to_ref().get()\n", + "\n", + "remote_y" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/weave/legacy/examples/experimental/skip_test/Composable Python panels.ipynb b/weave/legacy/examples/experimental/skip_test/Composable Python panels.ipynb new file mode 100644 index 00000000000..2a3d08c47be --- /dev/null +++ b/weave/legacy/examples/experimental/skip_test/Composable Python panels.ipynb @@ -0,0 +1,79 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "6f2db98c", + "metadata": {}, + "source": [ + "## This demo is not currently working" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5b582daf", + "metadata": {}, + "outputs": [], + "source": [ + "import weave" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "06ea5a95", + "metadata": {}, + "outputs": [], + "source": [ + "container = weave.legacy.weave.panels.Container()\n", + "\n", + "# A couple variables to hold our slider values (this is unnecessary boilerplate that I can\n", + "# get rid of)\n", + "var_a = container.config.add_variable('slider_value_a', weave.types.Number(), 5)\n", + "var_b = container.config.add_variable('slider_value_b', weave.types.Number(), 19)\n", + "\n", + "# Create two sliders in the Container\n", + "container.config.add_panel(\n", + " weave.legacy.weave.panels.Slider(input_node=var_a, config=weave.legacy.weave.panels.SliderConfig(0, 100, 0.1)))\n", + "container.config.add_panel(\n", + " weave.legacy.weave.panels.Slider(input_node=var_b, config=weave.legacy.weave.panels.SliderConfig(-1000, 1000, 1)))\n", + "\n", + "# Use a Number panel to show the results of adding the two sliders together\n", + "container.config.add_panel(\n", + " weave.legacy.weave.panels.Number(input_node=var_a + var_b)\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9423cf32", + "metadata": {}, + "outputs": [], + "source": [ + "weave.show(container)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/weave/legacy/examples/experimental/skip_test/Confusion Matrix.ipynb b/weave/legacy/examples/experimental/skip_test/Confusion Matrix.ipynb new file mode 100644 index 00000000000..2aabd645afa --- /dev/null +++ b/weave/legacy/examples/experimental/skip_test/Confusion Matrix.ipynb @@ -0,0 +1,288 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "51500f80", + "metadata": {}, + "outputs": [], + "source": [ + "import random\n", + "import typing\n", + "import itertools\n", + "from PIL import Image\n", + "import weave\n", + "from weave.legacy.weave import storage\n", + "from weave.legacy.weave import weave_internal\n", + "#weave.use_frontend_devmode()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b6e5e1c9", + "metadata": {}, + "outputs": [], + "source": [ + "# Not really model predictions but it'll work for now\n", + "import string\n", + "import hashlib\n", + "\n", + "def simple_hash(n, b):\n", + " return int.from_bytes(hashlib.sha256(str(n).encode()).digest(), \"little\") % b\n", + "\n", + "def create_data(n_rows, n_extra_cols, images=False):\n", + " inner_count = int(n_rows / 25)\n", + " base_im = Image.linear_gradient('L').resize((32, 32))\n", + " x_choices = string.ascii_lowercase\n", + " extra_cols = [chr(ord('a') + i) for i in range(n_extra_cols)]\n", + " ims = []\n", + " for i, (rotate, shear, _) in enumerate(\n", + " itertools.product(range(5), range(5), range(inner_count))\n", + " ):\n", + " im ={\n", + " 'rotate': rotate,\n", + " 'shear': shear,\n", + " 'y': x_choices[simple_hash(i**13, 5)],\n", + " 'x': x_choices[simple_hash(i**13, 11)]\n", + " }\n", + " if images:\n", + " im['image'] = (base_im\n", + " .rotate(rotate * 4)\n", + " .transform((32, 32), Image.AFFINE, (1, shear / 10, 0, 0, 1, 0), Image.BICUBIC))\n", + " for j, col in enumerate(extra_cols):\n", + " im[col] = x_choices[simple_hash(i*13**j, 11)]\n", + " ims.append(im)\n", + " return ims\n", + "\n", + "ims = create_data(100, 1, True)\n", + "#ims = storage.to_arrow(ims)\n", + "#ims = weave.save(ims)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2da45914", + "metadata": {}, + "outputs": [], + "source": [ + "weave.show(ims)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "49501ff4", + "metadata": {}, + "outputs": [], + "source": [ + "plot = weave.legacy.weave.panels.Plot(ims)\n", + "plot.set_x(lambda row: row['rotate'])\n", + "plot.set_y(lambda row: row['x'])\n", + "plot.set_tooltip(lambda row: row['image'])\n", + "weave.show(plot)" + ] + }, + { + "cell_type": "markdown", + "id": "6583e01c", + "metadata": {}, + "source": [ + "## Facet is fun!\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "090d8ab8", + "metadata": {}, + "outputs": [], + "source": [ + "# This is one way to build a multi-confusion matrix.\n", + "\n", + "facet = weave.legacy.weave.panels.Facet(\n", + " input_node=ims,\n", + " x=lambda im: im['rotate'],\n", + " y=lambda im: im['shear'],\n", + " select=lambda cell: weave.legacy.weave.panels.Plot(\n", + " input_node=cell.groupby(lambda row: row['y']),\n", + " x=lambda group: group.count(),\n", + " y=lambda group: group.key(),\n", + " label=lambda group: group.key(),\n", + " tooltip=lambda group: group.map(lambda r: r['image']),\n", + " mark='bar',\n", + " no_axes=True,\n", + " no_legend=True\n", + " )\n", + ")\n", + "\n", + "weave.show(facet)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e987f626", + "metadata": {}, + "outputs": [], + "source": [ + "# @weave.op()\n", + "# def confusion_matrix(inp: typing.Any, guess_col: str, truth_col: str, compare_col: str) -> weave.legacy.weave.panels.Facet:\n", + "# return weave.legacy.weave.panels.Facet(\n", + "# input_node=inp,\n", + "# x=lambda i: i[guess_col],\n", + "# y=lambda i: i[truth_col],\n", + "# select=lambda cell: weave.legacy.weave.panels.Plot(\n", + "# input_node=cell.groupby(\n", + "# weave.define_fn({'row': cell.type.object_type}, lambda row: row[compare_col])),\n", + "# x=lambda group: group.count(),\n", + "# y=lambda group: group.key(),\n", + "# label=lambda group: group.key(),\n", + "# mark='bar',\n", + "# no_axes=True,\n", + "# no_legend=True\n", + "# )\n", + "# )\n", + "\n", + "# An example of a Panel returning op. This (sort of) works but there are lots of \n", + "\n", + "@weave.op()\n", + "def confusion_matrix(inp: typing.Any, guess_col: str, truth_col: str, compare_col: str) -> weave.legacy.weave.panels.Facet:\n", + " return weave.legacy.weave.panels.Facet(\n", + " input_node=inp,\n", + " x=lambda i: i[guess_col],\n", + " y=lambda i: i[truth_col],\n", + " select=lambda cell: cell.count()\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9c800bac", + "metadata": {}, + "outputs": [], + "source": [ + "demos.confusion_matrix(ims, 'rotate', 'x', 'y')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7eff6907", + "metadata": {}, + "outputs": [], + "source": [ + "# Other explorations\n", + "\n", + "\n", + "# weave.show(facet)\n", + "\n", + "# # Not working yet, but playing with removing lambdas\n", + "# facet = weave.legacy.weave.panels.Facet(\n", + "# input_node=ims,\n", + "# x=ims['rotate'],\n", + "# y=ims['shear'],\n", + "# select=lambda cell: weave.legacy.weave.panels.Plot(\n", + "# input_node=cell.groupby(cell.row['y']),\n", + "# x=lambda group: group.count(),\n", + "# y=lambda group: group.key(),\n", + "# label=lambda group: group.key(),\n", + "# mark='bar',\n", + "# no_axes=True,\n", + "# no_legend=True\n", + "# )\n", + "# )\n", + "\n", + "# # Maybe another cool thing\n", + "# facet = weave.legacy.weave.panels.Facet(\n", + "# input_node=ims,\n", + "# x=lambda im: im['rotate'],\n", + "# y=lambda im: im['shear'],\n", + "# select=lambda cell_ims: cell_ims.groupby(cell.row['y'])\n", + "# .Plot(\n", + "# x=lambda group: group.count(),\n", + "# y=lambda group: group.key(),\n", + "# label=lambda group: group.key(),\n", + "# mark='bar',\n", + "# no_axes=True,\n", + "# no_legend=True\n", + "# )\n", + "# )\n", + "\n", + "# # OK actually its more like we just always want to drop the first argument\n", + "# # This is finally minimal, but we can't get autocomplete help.\n", + "# facet = ims.Facet(\n", + "# x=select('rotate'),\n", + "# y=select('shear'),\n", + "# select=groupby('y')\n", + "# .Plot(\n", + "# x=count(),\n", + "# y=group_key(),\n", + "# label=group_key(),\n", + "# mark=select('bar'),\n", + "# no_axes=True,\n", + "# no_legend=True\n", + "# )\n", + "# )\n", + "\n", + "# # Hmm.\n", + "# facet = ims\n", + "# .groupby(\n", + "# facet_x=select('rotate'),\n", + "# facet_y=select('shear'),\n", + "# y=select('run'))\n", + "# .count()\n", + "# .Plot()\n", + "\n", + "# facet = Plot(\n", + "# ims,\n", + "# facet_x=select('rotate'),\n", + "# facet_y=select('shear'),\n", + "# y=select('run')\n", + "# x=count()\n", + ")\n", + "\n", + "# TODO:\n", + "# - get rid of lambdas (ims.row could be a variable?)\n", + "# ... or we could just treat ims as the row variable when its assigned to a plot?\n", + "# - This is why react is a little nicer, component control flow isn't usually hidden\n", + "# away inside other components (framework style). Instead, you decide how you want to lay stuff\n", + "# out...\n", + "# - But we can achieve that here... We just need to make some lower level components.\n", + "# (Try IT!) Instead of PanelFacet, use PanelLayout or PanelGrid or something\n", + "# - see if we can get rid of groupby requirement in PanelPlot. You don't need it in Vega\n", + "# - also maybe look at Altair API?\n", + "#\n", + "# In react you'd do:\n", + "# const ConfusionMatrix = (ims) => (\n", + "# \n", + "# \n", + "# \n", + "# )" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/weave/legacy/examples/experimental/skip_test/Custom ops.ipynb b/weave/legacy/examples/experimental/skip_test/Custom ops.ipynb new file mode 100644 index 00000000000..6392f1d34fd --- /dev/null +++ b/weave/legacy/examples/experimental/skip_test/Custom ops.ipynb @@ -0,0 +1,149 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "f6c524b9", + "metadata": {}, + "outputs": [], + "source": [ + "import weave\n", + "import math\n", + "weave.use_frontend_devmode()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a257651b", + "metadata": {}, + "outputs": [], + "source": [ + "# Create our own useful op.\n", + "\n", + "import math\n", + "\n", + "@weave.op()\n", + "def my_func(x: float) -> float:\n", + " return math.sin(x / 0.2)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "89b6c0db", + "metadata": {}, + "outputs": [], + "source": [ + "# Now show it.\n", + "# Weave tries to guess the best way to show your data. Its first guess is not very good yet, so let's teach it!\n", + "# Click the gear icon to configure the Panel\n", + "# Try \"List of: Number\". You can page left to right using the controls that the bottom.\n", + "# Switch to \"Plot\"\n", + "# Pick \"row\" as the x value (todo this should be called \"i\" or \"item\" instead of row)\n", + "# Pick \"row * row\" as the y value.\n", + "# Cool, we made a curve!\n", + "\n", + "\n", + "x = [i / 1000.0 for i in range(0, 1000)]\n", + "\n", + "plot = weave.legacy.weave.panels.Plot(x)\n", + "plot.set_x(lambda row: row)\n", + "plot.set_y(lambda row: my_func(row))\n", + "weave.show(plot)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3d97c0fd", + "metadata": {}, + "outputs": [], + "source": [ + "run_data = []\n", + "xs = [i / 1000.0 for i in range(0, 1000)]\n", + "for run in range(3):\n", + " run_data.append({\n", + " 'id': 'id-%s' % run,\n", + " 'x': xs,\n", + " 'y': [math.sin(10 * x / (run+1)) for x in xs]\n", + " })\n", + "plot = weave.legacy.weave.panels.Plot(run_data)\n", + "plot.set_x(lambda row: row['x'])\n", + "plot.set_y(lambda row: row['y'])\n", + "plot.set_label(lambda row: row['id'])\n", + "weave.show(plot)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5f9b0741", + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "df = pd.DataFrame()\n", + "df['x'] = [i / 1000.0 for i in range(0, 1000)]\n", + "df['y1'] = df['x'].map(lambda x: math.sin(10 * x / 1))\n", + "df['y2'] = df['x'].map(lambda x: math.sin(10 * x / 2))\n", + "df['y3'] = df['x'].map(lambda x: math.sin(10 * x / 3))\n", + "\n", + "# df.melt converts to a table with three columns: x, series, y\n", + "df = df.melt(id_vars=['x'], value_vars=['y1', 'y2', 'y3'], var_name='series', value_name='y')\n", + "\n", + "multiple_y = df.to_dict('records')\n", + "plot = weave.legacy.weave.panels.Plot(multiple_y)\n", + "plot.set_x(lambda row: row['x'])\n", + "plot.set_y(lambda row: row['y'])\n", + "plot.set_label(lambda row: row['series'])\n", + "plot.set_mark('line')\n", + "weave.show(plot)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "07752656", + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "df = pd.DataFrame()\n", + "df['x'] = [i / 1000.0 for i in range(0, 1000)]\n", + "df['y1'] = df['x'].map(lambda x: math.sin(10 * x / 1))\n", + "df['y2'] = df['x'].map(lambda x: math.sin(10 * x / 2))\n", + "df['y3'] = df['x'].map(lambda x: math.sin(10 * x / 3))\n", + "multiple_y = df.to_dict('records')\n", + "\n", + "plot = weave.legacy.weave.panels.Plot(multiple_y)\n", + "plot.set_x(lambda row: row['x'])\n", + "plot.set_y(lambda row: [row['y1'], row['y2'], row['y3']])\n", + "plot.set_label(lambda row: ['y1', 'y2', 'y3'])\n", + "plot.set_mark('line')\n", + "weave.show(plot)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/weave/legacy/examples/experimental/skip_test/Diffusion explore 2.ipynb b/weave/legacy/examples/experimental/skip_test/Diffusion explore 2.ipynb new file mode 100644 index 00000000000..00723093f18 --- /dev/null +++ b/weave/legacy/examples/experimental/skip_test/Diffusion explore 2.ipynb @@ -0,0 +1,101 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "00411857", + "metadata": {}, + "outputs": [], + "source": [ + "import weave\n", + "from weave.legacy.weave import weave_internal\n", + "from weave.legacy.weave.ecosystem.all import langchain\n", + "weave.use_frontend_devmode()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "30c46465", + "metadata": {}, + "outputs": [], + "source": [ + "weave.legacy.weave.panels.Board(\n", + " vars={\n", + " \"chat_model\": langchain.chat_openai(\"gpt-3.5-turbo\", 0.7),\n", + " \"animals\": weave.legacy.weave.panels.Query(\n", + " [\"koala\", \"giraffe\", \"leopard\"],\n", + " text=lambda item: item\n", + " ),\n", + " \"objects\": weave.legacy.weave.panels.Query(\n", + " [\"spoon\", \"guitar\"],\n", + " text=lambda item: item\n", + " ),\n", + " \"prompts\": weave.legacy.weave.panels.Query([\n", + " \"A {animal} with an {object}\",\n", + " \"A 20-word story about a {animal} with an {object}\",\n", + " \"You are creative writer. Describe a scene with a {animal} and a {object}. Choose an art style and use adjectives that evoke it.\"\n", + " ],\n", + " text=lambda item: item),\n", + " \"combos\": lambda animals, objects, prompts:\n", + " weave.legacy.weave.ops.cross_product(weave.legacy.weave.ops.dict_(\n", + " animal=animals.pinned_data(),\n", + " object=objects.pinned_data(),\n", + " prompt=prompts.pinned_data())),\n", + "\n", + " },\n", + " panels=[\n", + " weave.legacy.weave.panels.BoardPanel(\n", + " lambda chat_model, combos: weave.legacy.weave.panels.Facet(\n", + " combos,\n", + " cell_size=(256, 256),\n", + " x=lambda item: item['animal'] + ' ' + item['object'],\n", + " y=lambda item: item['prompt'],\n", + " select=lambda items: \n", + " weave.legacy.weave.panels.Group(\n", + " vars={\n", + " 'gen_text': chat_model.predict(\n", + " items[0]['prompt'].format(weave.legacy.weave.ops.dict_(\n", + " animal=items[0]['animal'],\n", + " object=items[0]['object'],\n", + " ))),\n", + " },\n", + " items={\n", + " 'text': lambda gen_text: gen_text,\n", + " 'image': lambda gen_text: gen_text.stable_diffusion()\n", + " }\n", + " )\n", + " ),\n", + " layout=weave.legacy.weave.panels.BoardPanelLayout(x=0, y=0, w=24, h=24)\n", + " )\n", + "# weave.legacy.weave.panels.BoardPanel(\n", + "# lambda col_names: weave.legacy.weave.panels.Table(col_names, columns=[lambda col: col]),\n", + "# layout=weave.legacy.weave.panels.BoardPanelLayout(x=0, y=12, w=12, h=6)\n", + "# ),\n", + "# weave.legacy.weave.panels.BoardPanel(\n", + "# lambda row_names: weave.legacy.weave.panels.Table(row_names, columns=[lambda row: row]),\n", + "# layout=weave.legacy.weave.panels.BoardPanelLayout(x=12, y=12, w=12, h=6)\n", + "# ),\n", + " ]\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c8d3d23b", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/weave/legacy/examples/experimental/skip_test/Diffusion explore.ipynb b/weave/legacy/examples/experimental/skip_test/Diffusion explore.ipynb new file mode 100644 index 00000000000..9289d57e217 --- /dev/null +++ b/weave/legacy/examples/experimental/skip_test/Diffusion explore.ipynb @@ -0,0 +1,169 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "6048a38a", + "metadata": {}, + "source": [ + "# Diffusion exploration\n", + "\n", + "Progammatically build a Weave Dashboard that generates a grid of increasingly magical images of unlikely pairs of animals.\n", + "\n", + "## Play with parameters\n", + "\n", + "* topic: change what the pairs are (animals? plants? minerals?)\n", + "* num_cols: change the number of levels/granularity of increasing magic\n", + "* num_rows: change how many groupings of the subject are generated :)\n", + "* prompts: edit the text of the prompt in col_names or row_names to modify this setup :)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "00411857", + "metadata": {}, + "outputs": [], + "source": [ + "import weave\n", + "from weave.legacy.weave import weave_internal\n", + "from weave.legacy.weave.ecosystem.all import langchain" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "30c46465", + "metadata": {}, + "outputs": [], + "source": [ + "weave.legacy.weave.panels.Board(\n", + " vars={\n", + " \"chat_model\": langchain.chat_openai(\"gpt-3.5-turbo\", 0.7),\n", + " \"topic\": \"animals\",\n", + " \"num_cols\": 2,\n", + " \"col_names\": lambda chat_model, num_cols: weave.legacy.weave.ops.range(0, num_cols, 1).map(\n", + " lambda i: chat_model.predict(weave_internal.const(\"You are a creative writer. You've come up with \")\n", + " + i.toString()\n", + " + \" ordered descriptive phrases (3 words or fewer) that represent how magical something is, from least magical to most. Phrase \"\n", + " + i.toString()\n", + " + \" is:\")\n", + " ),\n", + " \"num_rows\": 2,\n", + " \"row_names\": lambda chat_model, topic, num_rows: weave.legacy.weave.ops.range(0, num_rows, 1).map(\n", + " lambda i: chat_model.predict(weave_internal.const(\"You are a creative writer. You've come up with \")\n", + " + i.toString()\n", + " + \" \" + topic\n", + " + \" that are unlikely to be seen together. Number \"\n", + " + i.toString()\n", + " + \" is: \")\n", + " ),\n", + "\n", + " },\n", + " panels=[\n", + " weave.legacy.weave.panels.BoardPanel(\n", + " lambda num_rows, num_cols, row_names, col_names: weave.legacy.weave.panels.Facet(\n", + " weave.legacy.weave.ops.range(0, num_rows * num_cols, 1),\n", + " cell_size=(192, 192),\n", + " x=lambda i: col_names[i % num_cols],\n", + " y=lambda i: row_names[(i / num_cols).floor()],\n", + " select=lambda cell_values: (\n", + " weave_internal.const(\"cute hd cartoon of \")\n", + " + row_names[(cell_values[0] / num_cols).floor()]\n", + " + \" \"\n", + " + col_names[(cell_values[0] % num_cols)]).stable_diffusion()\n", + " ),\n", + " layout=weave.legacy.weave.panels.BoardPanelLayout(x=0, y=0, w=24, h=12)\n", + " ),\n", + " weave.legacy.weave.panels.BoardPanel(\n", + " lambda col_names: weave.legacy.weave.panels.Table(col_names, columns=[lambda col: col]),\n", + " layout=weave.legacy.weave.panels.BoardPanelLayout(x=0, y=12, w=12, h=6)\n", + " ),\n", + " weave.legacy.weave.panels.BoardPanel(\n", + " lambda row_names: weave.legacy.weave.panels.Table(row_names, columns=[lambda row: row]),\n", + " layout=weave.legacy.weave.panels.BoardPanelLayout(x=12, y=12, w=12, h=6)\n", + " ),\n", + " ]\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c0f6057c", + "metadata": {}, + "outputs": [], + "source": [ + "weave.save(\n", + "\"\"\"\n", + "weave.legacy.weave.panels.Board(\n", + " vars={\n", + " \"chat_model\": langchain.chat_openai(\"gpt-3.5-turbo\", 0.7),\n", + " \"num_cols\": 2,\n", + " \"col_names\": lambda chat_model, num_cols: weave.legacy.weave.ops.range(0, num_cols, 1).map(\n", + " lambda i: chat_model.predict(weave_internal.const(\"You are a creative writer. You've come up with \")\n", + " + i.toString()\n", + " + \" ordered descriptive phrases (3 words or fewer) that represent how magical something is, from least magical to most. Phrase \"\n", + " + i.toString()\n", + " + \" is:\")\n", + " ),\n", + " \"num_rows\": 2,\n", + " \"row_names\": lambda chat_model, num_rows: weave.legacy.weave.ops.range(0, num_rows, 1).map(\n", + " lambda i: chat_model.predict(weave_internal.const(\"You are a creative writer. You've come up with \")\n", + " + i.toString()\n", + " + \" animals that are unlikely to be seen together. Animal \"\n", + " + i.toString()\n", + " + \" is: \")\n", + " ),\n", + "\n", + " },\n", + " panels=[\n", + " weave.legacy.weave.panels.BoardPanel(\n", + " lambda num_rows, num_cols, row_names, col_names: weave.legacy.weave.panels.Facet(\n", + " weave.legacy.weave.ops.range(0, num_rows * num_cols, 1),\n", + " cell_size=(192, 192),\n", + " x=lambda i: col_names[i % num_cols],\n", + " y=lambda i: row_names[(i / num_cols).floor()],\n", + " select=lambda cell_values: (\n", + " weave_internal.const(\"cute hd cartoon of \")\n", + " + row_names[(cell_values[0] / num_cols).floor()]\n", + " + \" \"\n", + " + col_names[(cell_values[0] % num_cols)]).stable_diffusion()\n", + " ),\n", + " layout=weave.legacy.weave.panels.BoardPanelLayout(x=0, y=0, w=24, h=12)\n", + " ),\n", + " weave.legacy.weave.panels.BoardPanel(\n", + " lambda col_names: weave.legacy.weave.panels.Table(col_names, columns=[lambda col: col]),\n", + " layout=weave.legacy.weave.panels.BoardPanelLayout(x=0, y=12, w=12, h=6)\n", + " ),\n", + " weave.legacy.weave.panels.BoardPanel(\n", + " lambda row_names: weave.legacy.weave.panels.Table(row_names, columns=[lambda row: row]),\n", + " layout=weave.legacy.weave.panels.BoardPanelLayout(x=12, y=12, w=12, h=6)\n", + " ),\n", + " ]\n", + ")\n", + "\"\"\", 'stable_diffusion_board_str')" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/weave/legacy/examples/experimental/skip_test/Diffusion story.ipynb b/weave/legacy/examples/experimental/skip_test/Diffusion story.ipynb new file mode 100644 index 00000000000..4a9a040968d --- /dev/null +++ b/weave/legacy/examples/experimental/skip_test/Diffusion story.ipynb @@ -0,0 +1,122 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "00411857", + "metadata": {}, + "outputs": [], + "source": [ + "import weave\n", + "from weave.legacy.weave import weave_internal\n", + "from weave.legacy.weave.ecosystem.all import langchain\n", + "weave.use_frontend_devmode()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "30c46465", + "metadata": {}, + "outputs": [], + "source": [ + "weave.legacy.weave.panels.Board(\n", + " vars={\n", + " 'gpt35': langchain.chat_openai(\"gpt-3.5-turbo\", 0.7),\n", + " 'gpt4': langchain.chat_openai(\"gpt-4\", 0.7),\n", + " 'chat_model': lambda gpt4, gpt35: gpt4,\n", + " 'director': 'Wes Anderson',\n", + " 'director_emotion': 'worldy',\n", + " 'film_style': 'rollerblading documentary',\n", + " 'number_of_panes': 4,\n", + " 'story_gen_prompt': \"\"\"\n", + " You are {director} creating the next {film_style} film. Today you're feeling {director_emotion}.\n", + " In initial sketches, you write a {number_of_panes} step sketch of the plot of your film.\n", + " You include specific details about how the plot proceeds.\n", + " What do you write? Since you are so creative, you can overcome any limitations you might\n", + " think you have when it comes to writing style. In this case, you write your outline as a json list\n", + " with one item for each step.\n", + " \"\"\",\n", + " 'prompt': lambda director, director_emotion, film_style, number_of_panes, story_gen_prompt: story_gen_prompt.format(weave.legacy.weave.ops.dict_(\n", + " director= director,\n", + " director_emotion=director_emotion,\n", + " film_style=film_style,\n", + " number_of_panes=number_of_panes)),\n", + " 'storyline': lambda prompt, chat_model: chat_model.predict(prompt),\n", + " 'storyline_parsed': lambda storyline: storyline.json_parse_list(),\n", + " 'panel_prompt': 'A storyboard pane for a movie by {director} for their next {film_style} film, The scene description is: {panel}',\n", + " 'storyboard_panes': lambda storyline_parsed, panel_prompt, director, film_style:\n", + " storyline_parsed.map(lambda row:\n", + " panel_prompt.format(weave.legacy.weave.ops.dict_(\n", + " panel=row.json_dumps(),\n", + " director=director,\n", + " film_style=film_style,\n", + " ))).stable_diffusion()\n", + " },\n", + " panels=[ \n", + " weave.legacy.weave.panels.BoardPanel(\n", + " lambda storyline_parsed: storyline_parsed,\n", + " layout=weave.legacy.weave.panels.BoardPanelLayout(x=0, y=0, w=24, h=6)\n", + " ),\n", + " weave.legacy.weave.panels.BoardPanel(\n", + " lambda storyboard_panes: weave.legacy.weave.panels.Each(storyboard_panes),\n", + " layout=weave.legacy.weave.panels.BoardPanelLayout(x=0, y=0, w=24, h=12)\n", + " )\n", + " ]\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c8d3d23b", + "metadata": {}, + "outputs": [], + "source": [ + "# imperative version\n", + "\n", + "gpt35 = langchain.chat_openai(\"gpt-3.5-turbo\", 0.7)\n", + "gpt4 = langchain.chat_openai(\"gpt-4\", 0.7)\n", + "chat_model = gpt4\n", + "\n", + "director = 'Wes Anderson'\n", + "film_style = 'great american'\n", + "number_of_panes = 4\n", + "story_gen_prompt = weave.save(\"\"\"\n", + "You are {director} creating the next {film_style} film.\n", + "In initial sketches, you write a {number_of_panes} step sketch of the plot of your film.\n", + "You include specific details about how the plot proceeds.\n", + "What do you write? Since you are so creative, you can overcome any limitations you might\n", + "think you have when it comes to writing style. In this case, you write your outline as a json list\n", + "with one item for each step.\n", + "\"\"\",\n", + " name='story_gen_prompt')\n", + "storyline = gpt35.predict(story_gen_prompt.format({\n", + " 'director': director,\n", + " 'film_style': film_style,\n", + " 'number_of_panes': number_of_panes}))\n", + "storyline_parsed = storyline.json_parse()\n", + "#storyline_parsed" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6fddc9a8", + "metadata": {}, + "outputs": [], + "source": [ + "#weave.legacy.weave.panels.Each(storyline_parsed.map(lambda row: row.json_dumps()).stable_diffusion())" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/weave/legacy/examples/experimental/skip_test/Docbot.ipynb b/weave/legacy/examples/experimental/skip_test/Docbot.ipynb new file mode 100644 index 00000000000..c754aef88f5 --- /dev/null +++ b/weave/legacy/examples/experimental/skip_test/Docbot.ipynb @@ -0,0 +1,329 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "ef0c98ad", + "metadata": {}, + "outputs": [], + "source": [ + "import pathlib\n", + "import weave\n", + "weave.use_frontend_devmode()\n", + "from weave.legacy.weave.ecosystem import langchain" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8f7192c3", + "metadata": {}, + "outputs": [], + "source": [ + "neg_feedback = [\"Is there a way that I can embed an iframe component in Weights and Biases report?\",\n", + " \"how do i create the pretty sweeps plot?\",\n", + " \"I'm getting error 400. What can I do?\",\n", + " \"How can I move a column in a table to the right?\",\n", + " \"In Prompts, how can I resize the Trace Timeline to make it bigger or full screen?\",\n", + " \"I have a question about exporting CSV files from a web panel. Whenever I do this, I always get two extra columns for MAX and MIN values, even if I only have one data curve. Does anyone know how to solve this issue?\",\n", + " \"artifacts cli command to upload a folder of images\",\n", + " \"Is there a recommended way to use Launch in an SLURM environment?\",\n", + " \"How to export a single chart's data using the API?\",\n", + " \"how can i login with a different wandb user?\"\n", + "]\n", + "\n", + "pos_feedback = [\"my logging doesn't seem to include errors when the training crashes, how do I change the logging level for wandb logging?\",\n", + " \"how can i get the data from my wandb run by querying my logs using python?\",\n", + " \"how do I fix an error with wandb Table construction from pandas dataframe: TypeError: Data row contained incompatible types\",\n", + " \"how can i make a heatmap using vega and plot it to wandb? what is the vegaspec?\",\n", + " \"is there a good way to join 2 tables together programmatically?\",\n", + " \"I have a question about sweeps. How can you constrain relationship between parameters. For example, I now that if num_layers * hidden_dim is large, I'll run out of GPU memory. So, also I would like to explore some hyperparameter space, there are some combination I know will fail. optuna as a way to do that: you can throw an special exception to cancel a run during a sweep, so that it is not recorded. Is there something similar in W&B, or another way of pruning unwanted combination of hyperparameters?\",\n", + " \"where can I find my run_id\",\n", + " \"How do I group runs?\",\n", + " \"I am using the Hugging Face trainer to train a GPT-2 model. How can I log in wandb the results of the model in each evaluation?\",\n", + " \"I am looking to finetune LLAMA on my own dataset using OpenAI, can you give me examples on how to do this?\"\n", + " ]\n", + "\n", + "all_qs = [\n", + " {'question': q, 'feedback': 'positive'} for q in pos_feedback] + [\n", + " {'question': q, 'feedback': 'negative'} for q in neg_feedback]\n", + "\n", + "questions = weave.save(all_qs, 'eval_questions')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "03a68bf9", + "metadata": {}, + "outputs": [], + "source": [ + "questions" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f6e1c366", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain.docstore.document import Document\n", + "from langchain.text_splitter import (\n", + " MarkdownTextSplitter,\n", + " PythonCodeTextSplitter,\n", + " TokenTextSplitter,\n", + ")\n", + "# Get markdown files from our docs repo\n", + "\n", + "# Checkout of our docs repo: https://github.com/wandb/docodile/\n", + "DOC_DIR = '/Users/shawn/code2/docodile'\n", + "DOC_SUFFIX = '.md'\n", + "\n", + "docs = []\n", + "for file in pathlib.Path(DOC_DIR).glob('**/*' + DOC_SUFFIX):\n", + " with file.open('r') as f:\n", + " # store them as langchain Document objects\n", + " docs.append(Document(page_content=f.read(), metadata={'path': file.name}))\n", + "docs = MarkdownTextSplitter().split_documents(docs)\n", + "docs = TokenTextSplitter().split_documents(docs)\n", + "\n", + "docs = weave.save(docs, 'wandb-docs')\n", + "docs" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "624e0b9e", + "metadata": {}, + "outputs": [], + "source": [ + "# Vector store with langchain\n", + "from langchain.vectorstores import VectorStore, FAISS\n", + "from langchain.embeddings.openai import OpenAIEmbeddings\n", + "embeddings = OpenAIEmbeddings()\n", + "vector_store = FAISS.from_documents(weave.use(docs), embeddings)\n", + "\n", + "weave.save(vector_store)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a97a3a9c", + "metadata": {}, + "outputs": [], + "source": [ + "# Vector store with Weave\n", + "\n", + "# Stuff you can do:\n", + "# - .similarity_search()\n", + "# - but currently there is a crash because Row.Group tries to render and fails. Need to switch the\n", + "# panel to Table and then change column to row.__getattr__('page_content')\n", + "# - .document_embeddings\n", + "# - this gets the embeddings out of FAISS, and also performs FAISS' k-means with 20 clusters\n", + "# - switch to projection.plot\n", + "# TODO:\n", + "# - give control over k for k-means\n", + "\n", + "from weave.legacy.weave.ecosystem import langchain\n", + "\n", + "vector_store_node = langchain.faiss_from_documents(docs, langchain.openai_embeddings())\n", + "vector_store_node" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bd31e988", + "metadata": {}, + "outputs": [], + "source": [ + "# With langchain\n", + "from langchain.chat_models import ChatOpenAI\n", + "from langchain.chains import RetrievalQA\n", + "model_gpt_35_temp07 = RetrievalQA.from_chain_type(\n", + " llm=ChatOpenAI(model_name='gpt-3.5-turbo', temperature=0.7),\n", + " chain_type='stuff',\n", + " retriever=vector_store.as_retriever()\n", + " )\n", + "model = model_gpt_35_temp07\n", + "model = weave.save(model_gpt_35_temp07, 'mymodel')\n", + "\n", + "#model.run('hello')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "694ce5d6", + "metadata": {}, + "outputs": [], + "source": [ + "# With weave\n", + "from weave.legacy.weave.ecosystem import langchain\n", + "qa = langchain.retrieval_qa_from_chain_type(\n", + " langchain.chat_openai('gpt-3.5-turbo', 0.7),\n", + " 'stuff',\n", + " vector_store_node)\n", + "qa.run('hello')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "27fc1618", + "metadata": {}, + "outputs": [], + "source": [ + "# from langchain.chat_models import ChatOpenAI\n", + "# from langchain.chains import RetrievalQA\n", + "model_02 = RetrievalQA.from_chain_type(\n", + " llm=ChatOpenAI(model_name='gpt-3.5-turbo', temperature=0.2),\n", + " chain_type='stuff',\n", + " retriever=vector_store.as_retriever()\n", + " )\n", + "model_07 = RetrievalQA.from_chain_type(\n", + " llm=ChatOpenAI(model_name='gpt-3.5-turbo', temperature=0.7),\n", + " chain_type='stuff',\n", + " retriever=vector_store.as_retriever()\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f18ae43f", + "metadata": {}, + "outputs": [], + "source": [ + "# Two with LC and weave.save\n", + "\n", + "models = [model_02, model_07]\n", + "models = weave.save(models, 'docbot-models')\n", + "models.run('hello')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "34c3e345", + "metadata": {}, + "outputs": [], + "source": [ + "# You can map over questions\n", + "weave.legacy.weave.panels.Table(questions.limit(3), columns=[\n", + " lambda q: q['question'],\n", + " lambda q: models.run(q['question'])])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "876bc738", + "metadata": {}, + "outputs": [], + "source": [ + "# Interactive evaluation and exploration dashboard\n", + "\n", + "weave.legacy.weave.panels.Board(\n", + " vars={\n", + " 'documents': docs,\n", + " 'questions': questions.limit(2),\n", + " 'embeddings': langchain.openai_embeddings(),\n", + " 'vector_store': lambda embeddings, documents: langchain.faiss_from_documents(documents, embeddings),\n", + " 'doc_embeddings': lambda vector_store: vector_store.document_embeddings(),\n", + " #'models': models,\n", + " 'model_a': lambda vector_store: langchain.retrieval_qa_from_chain_type(\n", + " langchain.chat_openai('gpt-3.5-turbo', 0.2),\n", + " 'stuff',\n", + " vector_store),\n", + " 'model_b': lambda vector_store: langchain.retrieval_qa_from_chain_type(\n", + " langchain.chat_openai('gpt-3.5-turbo', 0.7),\n", + " 'stuff',\n", + " vector_store),\n", + " 'models': lambda model_a, model_b: weave.legacy.weave.ops.make_list(a=model_a, b=model_b),\n", + " 'projection': lambda doc_embeddings: doc_embeddings.projection2D(\n", + " 'pca',\n", + " 'single',\n", + " ['embedding'],\n", + " {'pca': {},\n", + " 'tsne': {\n", + " 'perplexity': 30,\n", + " 'learningRate': 10,\n", + " 'iterations': 25\n", + " },\n", + " 'umap': {\n", + " 'neighbors': 15,\n", + " 'minDist': 0.1,\n", + " 'spread': 1.0\n", + " }\n", + " }),\n", + " },\n", + " panels=[ \n", + " weave.legacy.weave.panels.BoardPanel(\n", + " lambda models: weave.legacy.weave.panels.Each(models.run(\"What is Weave?\")),\n", + " layout=weave.legacy.weave.panels.BoardPanelLayout(x=0, y=0, w=24, h=6)\n", + " ),\n", + " weave.legacy.weave.panels.BoardPanel(\n", + " lambda model_a, model_b: weave.legacy.weave.panels.Table(questions,\n", + " columns=[\n", + " lambda question: question['question'],\n", + " lambda question: question['feedback'],\n", + " weave.legacy.weave.panels.TableColumn(\n", + " lambda question: model_a.run(question['question']).result,\n", + " name='model_a'\n", + " ),\n", + " weave.legacy.weave.panels.TableColumn(\n", + " lambda question: model_b.run(question['question']).result,\n", + " name='model_b'\n", + " ),\n", + " ]),\n", + " layout=weave.legacy.weave.panels.BoardPanelLayout(x=0, y=6, w=24, h=6)\n", + " ), \n", + " weave.legacy.weave.panels.BoardPanel(\n", + " id='docs_projection',\n", + " panel=lambda projection: weave.legacy.weave.panels.Plot(\n", + " projection,\n", + " x=lambda row: row['projection.x'],\n", + " y=lambda row: row['projection.y'],\n", + " color=lambda row: row['source.cluster']\n", + " ),\n", + " layout=weave.legacy.weave.panels.BoardPanelLayout(x=0, y=12, w=12, h=6)\n", + " ),\n", + " weave.legacy.weave.panels.BoardPanel(\n", + " lambda docs_projection: docs_projection.selected_data(),\n", + " layout=weave.legacy.weave.panels.BoardPanelLayout(x=12, y=12, w=12, h=6)\n", + " ),\n", + " weave.legacy.weave.panels.BoardPanel(\n", + " lambda documents: weave.legacy.weave.panels.Table(documents,\n", + " columns=[\n", + " lambda doc: doc.page_content,\n", + " lambda doc: doc.metadata['path']\n", + " ]),\n", + " layout=weave.legacy.weave.panels.BoardPanelLayout(x=0, y=18, w=12, h=6)\n", + " ),\n", + " weave.legacy.weave.panels.BoardPanel(\n", + " lambda vector_store: weave.legacy.weave.panels.Table(vector_store.similarity_search('weave'),\n", + " columns=[\n", + " lambda doc: doc.page_content,\n", + " lambda doc: doc.metadata['path'] \n", + " ]),\n", + " layout=weave.legacy.weave.panels.BoardPanelLayout(x=12, y=18, w=12, h=6)\n", + " ),\n", + " ]\n", + ")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/weave/legacy/examples/experimental/skip_test/Hack demo.ipynb b/weave/legacy/examples/experimental/skip_test/Hack demo.ipynb new file mode 100644 index 00000000000..9d4eea67e0a --- /dev/null +++ b/weave/legacy/examples/experimental/skip_test/Hack demo.ipynb @@ -0,0 +1,117 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "6e89befe", + "metadata": {}, + "outputs": [], + "source": [ + "import typing\n", + "import weave\n", + "from weave.legacy.weave.ecosystem import hackdemo\n", + "\n", + "PROJECT = 'hackdemo'" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e0209514", + "metadata": {}, + "outputs": [], + "source": [ + "dataset = hackdemo.ca_housing_dataset(25000)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4893a4a8", + "metadata": {}, + "outputs": [], + "source": [ + "weave.show(dataset)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "35d52005", + "metadata": {}, + "outputs": [], + "source": [ + "@weave.op()\n", + "def table_summary(table: typing.Any) -> list[weave.legacy.weave.panels.Panel]:\n", + " if not table:\n", + " return\n", + " col_names = list(table[0].keys())\n", + " with weave.lazy_execution():\n", + " cols = {col_name: table.pick(col_name) for col_name in col_names}\n", + " panels = []\n", + " for col_name, col_values in cols.items():\n", + " panels.append(weave.legacy.weave.panels.LabeledItem(item=col_values, label=col_name))\n", + " return panels" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "42638af4", + "metadata": {}, + "outputs": [], + "source": [ + "data_card = hackdemo.Dataset(\n", + " name='California Housing',\n", + " created_by=hackdemo.User(name='Pace, R. Kelley'),\n", + " description='S&P Letters Data\\n\\nWe collected information on the variables using all the block groups in California from the 1990 Cens us. In this sample a block group on average includes 1425.5 individuals living in a geographically co mpact area. Naturally, the geographical area included varies inversely with the population density. W e computed distances among the centroids of each block group as measured in latitude and longitude. W e excluded all the block groups reporting zero entries for the independent and dependent variables. T he final data contained 20,640 observations on 9 variables. The dependent variable is ln(median house value).',\n", + " updated_at='August 25th 2021, at 3:36:29pm',\n", + " table=weave.use(dataset) \n", + ")\n", + "\n", + "saved_data_card = weave.save(data_card)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "578adec4", + "metadata": {}, + "outputs": [], + "source": [ + "#weave.publish(data_card, '%s/ca-housing-cadr' % PROJECT)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c35a4702", + "metadata": {}, + "outputs": [], + "source": [ + "hackdemo.ca_housing_dataset_card(saved_data_card)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/weave/legacy/examples/experimental/skip_test/KerasModel.ipynb b/weave/legacy/examples/experimental/skip_test/KerasModel.ipynb new file mode 100644 index 00000000000..f03a31c6e65 --- /dev/null +++ b/weave/legacy/examples/experimental/skip_test/KerasModel.ipynb @@ -0,0 +1,305 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "ad4e3073", + "metadata": {}, + "outputs": [], + "source": [ + "import weave\n", + "weave.use_frontend_devmode()\n", + "from weave.legacy.weave.ecosystem import keras as weave_keras" + ] + }, + { + "cell_type": "markdown", + "id": "2f3cbb0d", + "metadata": {}, + "source": [ + "# Train Language Model\n", + "\n", + "From: https://keras.io/examples/nlp/text_classification_from_scratch/" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ec47e09b", + "metadata": {}, + "outputs": [], + "source": [ + "import tensorflow as tf\n", + "import numpy as np" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d288f1b0", + "metadata": {}, + "outputs": [], + "source": [ + "!curl -O https://ai.stanford.edu/~amaas/data/sentiment/aclImdb_v1.tar.gz\n", + "!tar -xf aclImdb_v1.tar.gz\n", + "!rm -r aclImdb/train/unsup" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4e74e162", + "metadata": {}, + "outputs": [], + "source": [ + "batch_size = 32\n", + "raw_train_ds = tf.keras.preprocessing.text_dataset_from_directory(\n", + " \"aclImdb/train\",\n", + " batch_size=batch_size,\n", + " validation_split=0.2,\n", + " subset=\"training\",\n", + " seed=1337,\n", + ")\n", + "raw_val_ds = tf.keras.preprocessing.text_dataset_from_directory(\n", + " \"aclImdb/train\",\n", + " batch_size=batch_size,\n", + " validation_split=0.2,\n", + " subset=\"validation\",\n", + " seed=1337,\n", + ")\n", + "raw_test_ds = tf.keras.preprocessing.text_dataset_from_directory(\n", + " \"aclImdb/test\", batch_size=batch_size\n", + ")\n", + "\n", + "print(f\"Number of batches in raw_train_ds: {raw_train_ds.cardinality()}\")\n", + "print(f\"Number of batches in raw_val_ds: {raw_val_ds.cardinality()}\")\n", + "print(f\"Number of batches in raw_test_ds: {raw_test_ds.cardinality()}\")\n", + "\n", + "from tensorflow.keras.layers import TextVectorization\n", + "import string\n", + "import re\n", + "\n", + "# Having looked at our data above, we see that the raw text contains HTML break\n", + "# tags of the form '
'. These tags will not be removed by the default\n", + "# standardizer (which doesn't strip HTML). Because of this, we will need to\n", + "# create a custom standardization function.\n", + "def custom_standardization(input_data):\n", + " lowercase = tf.strings.lower(input_data)\n", + " stripped_html = tf.strings.regex_replace(lowercase, \"
\", \" \")\n", + " return tf.strings.regex_replace(\n", + " stripped_html, f\"[{re.escape(string.punctuation)}]\", \"\"\n", + " )\n", + "\n", + "\n", + "# Model constants.\n", + "max_features = 20000\n", + "embedding_dim = 128\n", + "sequence_length = 500\n", + "\n", + "# Now that we have our custom standardization, we can instantiate our text\n", + "# vectorization layer. We are using this layer to normalize, split, and map\n", + "# strings to integers, so we set our 'output_mode' to 'int'.\n", + "# Note that we're using the default split function,\n", + "# and the custom standardization defined above.\n", + "# We also set an explicit maximum sequence length, since the CNNs later in our\n", + "# model won't support ragged sequences.\n", + "vectorize_layer = TextVectorization(\n", + " standardize=custom_standardization,\n", + " max_tokens=max_features,\n", + " output_mode=\"int\",\n", + " output_sequence_length=sequence_length,\n", + ")\n", + "\n", + "vectorize_layer_non_custom = TextVectorization(\n", + "# standardize=custom_standardization,\n", + " max_tokens=max_features,\n", + " output_mode=\"int\",\n", + " output_sequence_length=sequence_length,\n", + ")\n", + "\n", + "# Now that the vocab layer has been created, call `adapt` on a text-only\n", + "# dataset to create the vocabulary. You don't have to batch, but for very large\n", + "# datasets this means you're not keeping spare copies of the dataset in memory.\n", + "\n", + "# Let's make a text-only dataset (no labels):\n", + "text_ds = raw_train_ds.map(lambda x, y: x)\n", + "# Let's call `adapt`:\n", + "vectorize_layer.adapt(text_ds)\n", + "vectorize_layer_non_custom.adapt(text_ds)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7e48486a", + "metadata": {}, + "outputs": [], + "source": [ + "def vectorize_text(text, label):\n", + " text = tf.expand_dims(text, -1)\n", + " return vectorize_layer(text), label\n", + "\n", + "\n", + "# Vectorize the data.\n", + "train_ds = raw_train_ds.map(vectorize_text)\n", + "val_ds = raw_val_ds.map(vectorize_text)\n", + "test_ds = raw_test_ds.map(vectorize_text)\n", + "\n", + "# Do async prefetching / buffering of the data for best performance on GPU.\n", + "train_ds = train_ds.cache().prefetch(buffer_size=10)\n", + "val_ds = val_ds.cache().prefetch(buffer_size=10)\n", + "test_ds = test_ds.cache().prefetch(buffer_size=10)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7f053ba5", + "metadata": {}, + "outputs": [], + "source": [ + "from tensorflow.keras import layers\n", + "\n", + "# A integer input for vocab indices.\n", + "inputs = tf.keras.Input(shape=(None,), dtype=\"int64\")\n", + "\n", + "# Next, we add a layer to map those vocab indices into a space of dimensionality\n", + "# 'embedding_dim'.\n", + "x = layers.Embedding(max_features, embedding_dim)(inputs)\n", + "x = layers.Dropout(0.5)(x)\n", + "\n", + "# Conv1D + global max pooling\n", + "x = layers.Conv1D(128, 7, padding=\"valid\", activation=\"relu\", strides=3)(x)\n", + "x = layers.Conv1D(128, 7, padding=\"valid\", activation=\"relu\", strides=3)(x)\n", + "x = layers.GlobalMaxPooling1D()(x)\n", + "\n", + "# We add a vanilla hidden layer:\n", + "x = layers.Dense(128, activation=\"relu\")(x)\n", + "x = layers.Dropout(0.5)(x)\n", + "\n", + "# We project onto a single unit output layer, and squash it with a sigmoid:\n", + "predictions = layers.Dense(1, activation=\"sigmoid\", name=\"predictions\")(x)\n", + "\n", + "text_model = tf.keras.Model(inputs, predictions)\n", + "\n", + "# Compile the model with binary crossentropy loss and an adam optimizer.\n", + "text_model.compile(loss=\"binary_crossentropy\", optimizer=\"adam\", metrics=[\"accuracy\"])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f177875d", + "metadata": {}, + "outputs": [], + "source": [ + "epochs = 1\n", + "\n", + "# Fit the model using the train and test datasets.\n", + "text_model.fit(train_ds, validation_data=val_ds, epochs=epochs)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a276fc85", + "metadata": {}, + "outputs": [], + "source": [ + "# A string input\n", + "inputs = tf.keras.Input(shape=(1,), dtype=\"string\")\n", + "# Turn strings into vocab indices\n", + "indices = vectorize_layer_non_custom(inputs)\n", + "# Turn vocab indices into predictions\n", + "outputs = text_model(indices)\n", + "\n", + "# Our end to end model\n", + "end_to_end_model = tf.keras.Model(inputs, outputs)\n", + "end_to_end_model.compile(\n", + " loss=\"binary_crossentropy\", optimizer=\"adam\", metrics=[\"accuracy\"]\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "06493feb", + "metadata": {}, + "outputs": [], + "source": [ + "saved_text_model = weave.save(end_to_end_model)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3040ac71", + "metadata": {}, + "outputs": [], + "source": [ + "weave_keras.call_string(saved_text_model, \"single test string\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "027e8660", + "metadata": {}, + "outputs": [], + "source": [ + "weave.legacy.weave.panels.Table([\n", + " {'input_str': \"This is a test\"},\n", + " {'input_str': \"This is a horrible test\"},\n", + " {'input_str': \"I love weave\"},\n", + " {'input_str': \"please work!\"},\n", + " {'input_str': \"I am a person in a house\"},\n", + "], columns=[\n", + " lambda row: row['input_str'],\n", + " lambda row: weave_keras.call_string(saved_text_model, row['input_str'])\n", + "])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f0e435bc", + "metadata": {}, + "outputs": [], + "source": [ + "# published_text_model = weave.save(end_to_end_model)\n", + "published_text_model = weave.publish(end_to_end_model)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8691e38a", + "metadata": {}, + "outputs": [], + "source": [ + "weave_keras.call_string(published_text_model, \"single test string\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/weave/legacy/examples/experimental/skip_test/LLMs Via Weave.ipynb b/weave/legacy/examples/experimental/skip_test/LLMs Via Weave.ipynb new file mode 100644 index 00000000000..f974b36f8ef --- /dev/null +++ b/weave/legacy/examples/experimental/skip_test/LLMs Via Weave.ipynb @@ -0,0 +1,111 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "2dc04fd0", + "metadata": {}, + "outputs": [], + "source": [ + "import weave\n", + "# weave.use_frontend_devmode()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "615e5163", + "metadata": {}, + "outputs": [], + "source": [ + "import weave\n", + "import weave.legacy.weave\n", + "panels = weave.legacy.weave.panels\n", + "\n", + "project = \"tim-test-llms-test-all-final-colab-01\"\n", + "entity = \"timssweeney\"\n", + "\n", + "# Create a Weave Query to get Traces\n", + "traces = (\n", + " weave.legacy.weave.ops\n", + " .project(entity, project)\n", + " .runs()\n", + " .history().map(\n", + " lambda row: row['langchain_trace'].dropna()\n", + " )\n", + " .concat()\n", + " .dropna()\n", + " .sort(\n", + " lambda row: weave.legacy.weave.ops.make_list(a=row.startTime()), \n", + " ['asc']\n", + " )\n", + ")\n", + "\n", + "# Create a table from the query\n", + "table = panels.Table(traces)\n", + "table_state = table.config.tableState\n", + "\n", + "# Add columns, set sorting, and specify panels for columns\n", + "table_state.add_column(lambda row: row.traceSummaryDict()[\"isSuccess\"], \"Success\")\n", + "start_time_col_id = table_state.add_column(lambda row: row.traceSummaryDict()[\"startTime\"].toTimestamp(), \"Timestamp\")\n", + "table_state.enable_sort(start_time_col_id, \"desc\")\n", + "\n", + "markdown_panel = panels.table_state.PanelDef('string', {'mode': 'markdown'})\n", + "table_state.add_column(lambda row: row.traceSummaryDict()[\"formattedInput\"], \"Input\", markdown_panel)\n", + "table_state.add_column(lambda row: row.traceSummaryDict()[\"formattedOutput\"], \"Output\", markdown_panel)\n", + "table_state.add_column(lambda row: row.traceSummaryDict()[\"formattedChain\"], \"Chain\", markdown_panel)\n", + "table_state.add_column(lambda row: row.traceSummaryDict()[\"error\"], \"Error\")\n", + "table_state.add_column(lambda row: row.traceSummaryDict()[\"modelHash\"], \"Model ID\")\n", + "\n", + "# Compose into a group with detail view below\n", + "dash = panels.Group(\n", + " items={\n", + " \"all_traces\": table,\n", + " \"selected_details\": lambda all_traces: panels.Card(\n", + " title='Trace Viewer',\n", + " subtitle='',\n", + " content=[\n", + " panels.CardTab(\n", + " name='Trace Timeline',\n", + " content=panels.PanelWBTraceTreeTraceViewer(all_traces.active_data())\n", + " ),\n", + " panels.CardTab(\n", + " name='Model Architecture',\n", + " content=panels.PanelWBTraceTreeModelViewer(all_traces.active_data())\n", + " ),\n", + "\n", + " ]\n", + " )\n", + " }\n", + ")\n", + "\n", + "# Publish to W&B\n", + "# weave.publish(dash, \"weave_ops/prompts_in_weave1\")\n", + "\n", + "# View locally\n", + "dash" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/weave/legacy/examples/experimental/skip_test/Model Cards.ipynb b/weave/legacy/examples/experimental/skip_test/Model Cards.ipynb new file mode 100644 index 00000000000..73760222547 --- /dev/null +++ b/weave/legacy/examples/experimental/skip_test/Model Cards.ipynb @@ -0,0 +1,79 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "c6b2e19b", + "metadata": {}, + "outputs": [], + "source": [ + "import weave\n", + "weave.use_frontend_devmode()\n", + "from weave.legacy.weave.ecosystem import models" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4b51e4d5", + "metadata": {}, + "outputs": [], + "source": [ + "# Declare an instance of a ModelCard\n", + "model_card = models.ModelCard(\n", + " model_name='GPT-3',\n", + " created_by=models.User(name='OpenAI'),\n", + " updated_at='August 25th 2021, at 3:36:29pm', # TODO: timestamp\n", + " model_type='Language model',\n", + " primary_metric=models.TargetMetric(name='acc', direction='up'),\n", + " application='Our API has been deployed in thousands of applications with tasks ranging from helping people learn new languages to solving complex classification problems',\n", + " example='...Slightly better example...'\n", + ")\n", + "model_card_ref = weave.save(model_card, 'my-model-card')\n", + "models.model_card_panel(model_card_ref)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ba518a7e", + "metadata": {}, + "outputs": [], + "source": [ + "# Publish it to W&B\n", + "model_card_wbref = weave.publish(model_card, name='my-model')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "32dd7d50", + "metadata": {}, + "outputs": [], + "source": [ + "weave.show(model_card_wbref)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/weave/legacy/examples/experimental/skip_test/Models.ipynb b/weave/legacy/examples/experimental/skip_test/Models.ipynb new file mode 100644 index 00000000000..176f5767541 --- /dev/null +++ b/weave/legacy/examples/experimental/skip_test/Models.ipynb @@ -0,0 +1,103 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "0c235474", + "metadata": {}, + "outputs": [], + "source": [ + "import weave\n", + "import xgboost\n", + "from weave.legacy.weave.ecosystem import shap\n", + "#weave.use_frontend_devmode()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "63a29a5f", + "metadata": {}, + "outputs": [], + "source": [ + "dataset = weave.use(shap.ca_housing_dataset(1))\n", + "dataset_arrow = weave.legacy.weave.ops.dataframe_to_arrow(dataset)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c5633045", + "metadata": {}, + "outputs": [], + "source": [ + "weave.show(dataset_arrow)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b6edbb55", + "metadata": {}, + "outputs": [], + "source": [ + "weave.publish(dataset_arrow, 'my-dataset-transform')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "dcccca4b", + "metadata": {}, + "outputs": [], + "source": [ + "weave.show(x)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "40f37c5a", + "metadata": {}, + "outputs": [], + "source": [ + "weave.legacy.weave.ops.get('wandb-artifact://shawn/weave_ops/my-dataset-transform:8c478776a5e01c3c051d')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7a0d6be5", + "metadata": {}, + "outputs": [], + "source": [ + "@weave.op()\n", + "def some_plotly_op():\n", + " return Plotly.plot(whatever)\n", + "\n", + "weave.name(some_plotly_op(), name='my-cool-plot')" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/weave/legacy/examples/experimental/skip_test/Monitor2.ipynb b/weave/legacy/examples/experimental/skip_test/Monitor2.ipynb new file mode 100644 index 00000000000..3b3cb66ccaf --- /dev/null +++ b/weave/legacy/examples/experimental/skip_test/Monitor2.ipynb @@ -0,0 +1,310 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "039f298a", + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "import weave\n", + "weave.use_frontend_devmode()\n", + "from weave.legacy.weave.ops_arrow.list_ import dataframe_to_arrow\n", + "from weave.legacy.weave.ecosystem import wandb" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5e0d6245", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import pandas as pd\n", + "import random\n", + "from faker import Faker\n", + "from datetime import timedelta, time\n", + "\n", + "# Generate the version schedule\n", + "def generate_version_schedule(start_date, end_date):\n", + " current_date = start_date\n", + " versions = ['1.0']\n", + " version_schedule = {}\n", + " while current_date <= end_date:\n", + " date_versions = []\n", + " for version in versions:\n", + " service_percent = random.uniform(0, 1)\n", + " date_versions.append((version, service_percent))\n", + "\n", + " version_schedule[current_date.date()] = date_versions\n", + " current_date += timedelta(days=1)\n", + " if random.random() < 0.10: # 5% chance to introduce a new version each day\n", + " new_version = f'{float(versions[-1])+0.1:.1f}'\n", + " versions.append(new_version)\n", + " if len(versions) > 1 and random.random() < 0.10:\n", + " versions.pop(0)\n", + " return version_schedule\n", + "\n", + "# Generate the latency schedule\n", + "def generate_latency_schedule(start_date, end_date):\n", + " latency_schedule = {}\n", + " for current_date in pd.date_range(start_date, end_date):\n", + " base_latency = random.uniform(0.1, 1)\n", + " day_factor = random.uniform(0.5, 1.5)\n", + " month_factor = random.uniform(0.5, 1.5)\n", + " latency = base_latency * day_factor * month_factor\n", + " latency_schedule[current_date.date()] = latency\n", + " return latency_schedule\n", + "\n", + "# Generate the cost schedule\n", + "def generate_cost_schedule(start_date, end_date, cost_change_date):\n", + " cost_schedule = {}\n", + " current_date = start_date\n", + " cost_per_token = 0.01\n", + " while current_date <= end_date:\n", + " if current_date >= cost_change_date:\n", + " cost_per_token = 0.005\n", + " cost_schedule[current_date.date()] = cost_per_token\n", + " current_date += timedelta(days=1)\n", + " return cost_schedule\n", + "\n", + "def generate_user_usage_schedule(start_date, end_date, users):\n", + " user_usage_schedule = []\n", + " for user in users:\n", + " current_date = start_date + timedelta(days=random.randrange(90))\n", + " while current_date <= end_date:\n", + " usage_periods = random.randint(1, 30)\n", + " for _ in range(usage_periods):\n", + " period_length_timedelta = timedelta(hours=random.randint(1, 24 * 7))\n", + " rate = random.uniform(0.1, 10)\n", + " user_usage_schedule.append((current_date, user, period_length_timedelta, rate))\n", + " current_date += period_length_timedelta # Increment current_date\n", + " if current_date > end_date:\n", + " break\n", + " return user_usage_schedule" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9d6a0318", + "metadata": {}, + "outputs": [], + "source": [ + "# Define our fake users\n", + "fake = Faker()\n", + "users = [fake.user_name() for _ in range(100)]\n", + "\n", + "# Read the file and generate prompts\n", + "with open('/Users/shawn/Downloads/t8.shakespeare.txt', 'r') as f:\n", + " lines = f.read().split('\\n')\n", + "\n", + "# Define the time range\n", + "start_date = pd.to_datetime('2023-01-01', utc=True)\n", + "end_date = pd.to_datetime('2023-03-31', utc=True)\n", + "cost_change_date = pd.to_datetime('2023-02-15', utc=True)\n", + "\n", + "# Generate the schedules\n", + "version_schedule = generate_version_schedule(start_date, end_date)\n", + "latency_schedule = generate_latency_schedule(start_date, end_date)\n", + "cost_schedule = generate_cost_schedule(start_date, end_date, cost_change_date)\n", + "user_usage_schedule = generate_user_usage_schedule(start_date, end_date, users)\n", + "\n", + "# Helper function to generate a random completion\n", + "def generate_completion(prompt):\n", + " words = prompt.split()\n", + " completion = ' '.join(random.choices(words, k=int(len(words)* (random.random() + 0.1) * 10)))\n", + " return completion\n", + "\n", + "#latency_schedule" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a672c782", + "metadata": {}, + "outputs": [], + "source": [ + "import tqdm\n", + "data = []\n", + "for usage in tqdm.tqdm(user_usage_schedule):\n", + " usage_date, user, usage_period, rate = usage\n", + " \n", + " end_date = usage_date + usage_period\n", + " increment = timedelta(hours=rate)\n", + " \n", + " while usage_date < end_date:\n", + " # Find the version that was active during this usage\n", + " if usage_date.date() not in version_schedule:\n", + " break\n", + " active_versions = version_schedule[usage_date.date()]\n", + " #active_versions = [(version, percent) for date, version, percent in version_schedule if date.date() == usage_date.date()]\n", + " # Normalize the service percentages\n", + " total_percent = sum([percent for version, percent in active_versions])\n", + " if total_percent == 0:\n", + " continue\n", + " normalized_percentages = [percent / total_percent for version, percent in active_versions]\n", + "\n", + " version = np.random.choice([v for v, p in active_versions], p=normalized_percentages)\n", + " \n", + " # Find the cost during this usage\n", + " cost_per_token = cost_schedule[usage_date.date()]\n", + " \n", + " # Find the average latency during this usage\n", + " latency = latency_schedule[usage_date.date()]\n", + " latency *= (0.9 + random.random() *.2)\n", + " \n", + " prompt = ' '.join(random.sample(lines, 10)) # Increase prompt size\n", + " completion = generate_completion(prompt)\n", + " prompt_tokens = len(prompt.split())\n", + " completion_tokens = len(completion.split())\n", + " api_cost = (prompt_tokens + completion_tokens) * cost_per_token\n", + "\n", + " data.append([usage_date, user, version, prompt, completion, \n", + " prompt_tokens, completion_tokens, api_cost, latency])\n", + " \n", + " usage_date += increment\n", + "\n", + "df = pd.DataFrame(data, columns=['timestamp', 'username', 'model_version', 'prompt', 'completion', \n", + " 'prompt_tokens', 'completion_tokens', 'api_cost', 'latency'])\n", + "\n", + "predictions = weave.save(dataframe_to_arrow(df), 'predictions')\n", + "predictions" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "57fc86e0", + "metadata": {}, + "outputs": [], + "source": [ + "predictions = weave.legacy.weave.ops.get(\"local-artifact:///predictions:latest/obj\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8049b6dd", + "metadata": {}, + "outputs": [], + "source": [ + "weave.legacy.weave.panels.Board(\n", + " vars={\n", + " 'all_preds': predictions,\n", + " 'x_max': weave.legacy.weave.ops.date_parse(str(end_date)),\n", + " 'x_min': lambda x_max: x_max - weave.legacy.weave.ops.days(5),\n", + " 'predictions': lambda x_min: predictions.filter(lambda pred: pred['timestamp'] > x_min)\n", + " },\n", + " panels=[\n", + " weave.legacy.weave.panels.BoardPanel(\n", + " lambda predictions, x_min, x_max: wandb.TimeSeries(\n", + " predictions,\n", + " x=lambda item: item[\"timestamp\"],\n", + " label=lambda item: item[\"model_version\"],\n", + " agg=lambda preds: preds.count(),\n", + " min_x=x_min,\n", + " max_x=x_max,\n", + " mark=\"bar\",\n", + " axis_labels={\"y\": \"num_preds\"},\n", + " ),\n", + " layout=weave.legacy.weave.panels.BoardPanelLayout(x=0, y=0, w=12, h=6)\n", + " ),\n", + " weave.legacy.weave.panels.BoardPanel(\n", + " lambda predictions, x_min, x_max: wandb.TimeSeries(\n", + " predictions,\n", + " x=lambda item: item[\"timestamp\"],\n", + " label=lambda item: item[\"username\"],\n", + " agg=lambda preds: preds.count(),\n", + " min_x=x_min,\n", + " max_x=x_max,\n", + " mark=\"bar\",\n", + " axis_labels={\"y\": \"num_preds\"},\n", + " ),\n", + " layout=weave.legacy.weave.panels.BoardPanelLayout(x=12, y=0, w=12, h=6)\n", + " ),\n", + " weave.legacy.weave.panels.BoardPanel(\n", + " lambda predictions, x_min, x_max: wandb.TimeSeries(\n", + " predictions,\n", + " x=lambda item: item[\"timestamp\"],\n", + " #label=lambda item: item[\"model_version\"],\n", + " agg=lambda preds: preds[\"api_cost\"].sum(),\n", + " min_x=x_min,\n", + " max_x=x_max,\n", + " mark=\"line\",\n", + " axis_labels={\"y\": \"api_cost\"},\n", + " ),\n", + " layout=weave.legacy.weave.panels.BoardPanelLayout(x=0, y=6, w=8, h=6)\n", + " ),\n", + " weave.legacy.weave.panels.BoardPanel(\n", + " lambda predictions, x_min, x_max: wandb.TimeSeries(\n", + " predictions,\n", + " x=lambda item: item[\"timestamp\"],\n", + " #label=lambda item: item[\"model_version\"],\n", + " agg=lambda preds: preds[\"prompt_tokens\"].sum() + preds['completion_tokens'].sum(),\n", + " min_x=x_min,\n", + " max_x=x_max,\n", + " mark=\"line\",\n", + " axis_labels={\"y\": \"total_tokens\"},\n", + " ),\n", + " layout=weave.legacy.weave.panels.BoardPanelLayout(x=8, y=6, w=8, h=6)\n", + " ),\n", + " weave.legacy.weave.panels.BoardPanel(\n", + " lambda predictions, x_min, x_max: wandb.TimeSeries(\n", + " predictions,\n", + " x=lambda item: item[\"timestamp\"],\n", + " #label=lambda item: item[\"model_version\"],\n", + " agg=lambda preds: preds[\"latency\"].avg(),\n", + " min_x=x_min,\n", + " max_x=x_max,\n", + " mark=\"line\",\n", + " axis_labels={\"y\": \"avg_latency\"},\n", + " ),\n", + " layout=weave.legacy.weave.panels.BoardPanelLayout(x=16, y=6, w=8, h=6)\n", + " ),\n", + " weave.legacy.weave.panels.BoardPanel(\n", + " lambda predictions, x_min, x_max: wandb.Distribution(\n", + " predictions,\n", + " value_fn=lambda pred: pred['api_cost'],\n", + " bin_size=0.2\n", + " ),\n", + " layout=weave.legacy.weave.panels.BoardPanelLayout(x=0, y=12, w=8, h=6)\n", + " ),\n", + " weave.legacy.weave.panels.BoardPanel(\n", + " lambda predictions, x_min, x_max: wandb.Distribution(\n", + " predictions,\n", + " value_fn=lambda pred: pred['prompt_tokens'] + pred['completion_tokens'],\n", + " bin_size=25\n", + " ),\n", + " layout=weave.legacy.weave.panels.BoardPanelLayout(x=8, y=12, w=8, h=6)\n", + " ),\n", + " weave.legacy.weave.panels.BoardPanel(\n", + " lambda predictions, x_min, x_max: wandb.Distribution(\n", + " predictions,\n", + " value_fn=lambda pred: pred['latency'],\n", + " bin_size=0.05\n", + " ),\n", + " layout=weave.legacy.weave.panels.BoardPanelLayout(x=16, y=12, w=8, h=6)\n", + " ),\n", + " weave.legacy.weave.panels.BoardPanel(\n", + " lambda predictions: predictions,\n", + " layout=weave.legacy.weave.panels.BoardPanelLayout(x=0, y=18, w=24, h=12)\n", + " )\n", + " ]\n", + ")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/weave/legacy/examples/experimental/skip_test/Monitor3.ipynb b/weave/legacy/examples/experimental/skip_test/Monitor3.ipynb new file mode 100644 index 00000000000..f6634410915 --- /dev/null +++ b/weave/legacy/examples/experimental/skip_test/Monitor3.ipynb @@ -0,0 +1,320 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "039f298a", + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "import weave\n", + "weave.use_frontend_devmode()\n", + "from weave.legacy.weave.ops_arrow.list_ import dataframe_to_arrow\n", + "from weave.legacy.weave.ecosystem import wandb" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5e0d6245", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import pandas as pd\n", + "import random\n", + "from faker import Faker\n", + "from datetime import timedelta, time\n", + "\n", + "# Generate the version schedule\n", + "def generate_version_schedule(start_date, end_date):\n", + " current_date = start_date\n", + " versions = ['1.0']\n", + " version_schedule = {}\n", + " while current_date <= end_date:\n", + " date_versions = []\n", + " for version in versions:\n", + " service_percent = random.uniform(0, 1)\n", + " date_versions.append((version, service_percent))\n", + "\n", + " version_schedule[current_date.date()] = date_versions\n", + " current_date += timedelta(days=1)\n", + " if random.random() < 0.10: # 5% chance to introduce a new version each day\n", + " new_version = f'{float(versions[-1])+0.1:.1f}'\n", + " versions.append(new_version)\n", + " if len(versions) > 1 and random.random() < 0.10:\n", + " versions.pop(0)\n", + " return version_schedule\n", + "\n", + "# Generate the latency schedule\n", + "def generate_latency_schedule(start_date, end_date):\n", + " latency_schedule = {}\n", + " for current_date in pd.date_range(start_date, end_date):\n", + " base_latency = random.uniform(0.1, 1)\n", + " day_factor = random.uniform(0.5, 1.5)\n", + " month_factor = random.uniform(0.5, 1.5)\n", + " latency = base_latency * day_factor * month_factor\n", + " latency_schedule[current_date.date()] = latency\n", + " return latency_schedule\n", + "\n", + "# Generate the cost schedule\n", + "def generate_cost_schedule(start_date, end_date, cost_change_date):\n", + " cost_schedule = {}\n", + " current_date = start_date\n", + " cost_per_token = 0.01\n", + " while current_date <= end_date:\n", + " if current_date >= cost_change_date:\n", + " cost_per_token = 0.005\n", + " cost_schedule[current_date.date()] = cost_per_token\n", + " current_date += timedelta(days=1)\n", + " return cost_schedule\n", + "\n", + "def generate_user_usage_schedule(start_date, end_date, users):\n", + " user_usage_schedule = []\n", + " for user in users:\n", + " current_date = start_date + timedelta(days=random.randrange(90))\n", + " while current_date <= end_date:\n", + " usage_periods = random.randint(1, 30)\n", + " for _ in range(usage_periods):\n", + " period_length_timedelta = timedelta(hours=random.randint(1, 24 * 7))\n", + " rate = random.uniform(0.1, 10)\n", + " user_usage_schedule.append((current_date, user, period_length_timedelta, rate))\n", + " current_date += period_length_timedelta # Increment current_date\n", + " if current_date > end_date:\n", + " break\n", + " return user_usage_schedule" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9d6a0318", + "metadata": {}, + "outputs": [], + "source": [ + "# Define our fake users\n", + "fake = Faker()\n", + "users = [fake.user_name() for _ in range(100)]\n", + "\n", + "# Read the file and generate prompts\n", + "with open('/Users/shawn/Downloads/t8.shakespeare.txt', 'r') as f:\n", + " lines = f.read().split('\\n')\n", + "\n", + "# Define the time range\n", + "start_date = pd.to_datetime('2023-01-01', utc=True)\n", + "end_date = pd.to_datetime('2023-03-31', utc=True)\n", + "cost_change_date = pd.to_datetime('2023-02-15', utc=True)\n", + "\n", + "# Generate the schedules\n", + "version_schedule = generate_version_schedule(start_date, end_date)\n", + "latency_schedule = generate_latency_schedule(start_date, end_date)\n", + "cost_schedule = generate_cost_schedule(start_date, end_date, cost_change_date)\n", + "user_usage_schedule = generate_user_usage_schedule(start_date, end_date, users)\n", + "\n", + "# Helper function to generate a random completion\n", + "def generate_completion(prompt):\n", + " words = prompt.split()\n", + " completion = ' '.join(random.choices(words, k=int(len(words)* (random.random() + 0.1) * 10)))\n", + " return completion\n", + "\n", + "#latency_schedule" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a672c782", + "metadata": {}, + "outputs": [], + "source": [ + "import tqdm\n", + "data = []\n", + "for usage in tqdm.tqdm(user_usage_schedule):\n", + " usage_date, user, usage_period, rate = usage\n", + " \n", + " end_date = usage_date + usage_period\n", + " increment = timedelta(hours=rate)\n", + " \n", + " while usage_date < end_date:\n", + " # Find the version that was active during this usage\n", + " if usage_date.date() not in version_schedule:\n", + " break\n", + " active_versions = version_schedule[usage_date.date()]\n", + " #active_versions = [(version, percent) for date, version, percent in version_schedule if date.date() == usage_date.date()]\n", + " # Normalize the service percentages\n", + " total_percent = sum([percent for version, percent in active_versions])\n", + " if total_percent == 0:\n", + " continue\n", + " normalized_percentages = [percent / total_percent for version, percent in active_versions]\n", + "\n", + " version = np.random.choice([v for v, p in active_versions], p=normalized_percentages)\n", + " \n", + " # Find the cost during this usage\n", + " cost_per_token = cost_schedule[usage_date.date()]\n", + " \n", + " # Find the average latency during this usage\n", + " latency = latency_schedule[usage_date.date()]\n", + " latency *= (0.9 + random.random() *.2)\n", + " \n", + " prompt = ' '.join(random.sample(lines, 10)) # Increase prompt size\n", + " completion = generate_completion(prompt)\n", + " prompt_tokens = len(prompt.split())\n", + " completion_tokens = len(completion.split())\n", + " api_cost = (prompt_tokens + completion_tokens) * cost_per_token\n", + "\n", + " data.append([usage_date, user, version, prompt, completion, \n", + " prompt_tokens, completion_tokens, api_cost, latency])\n", + " \n", + " usage_date += increment\n", + "\n", + "df = pd.DataFrame(data, columns=['timestamp', 'username', 'model_version', 'prompt', 'completion', \n", + " 'prompt_tokens', 'completion_tokens', 'api_cost', 'latency'])\n", + "\n", + "predictions = weave.save(dataframe_to_arrow(df), 'predictions')\n", + "predictions" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "57fc86e0", + "metadata": {}, + "outputs": [], + "source": [ + "predictions = weave.legacy.weave.ops.get(\"local-artifact:///predictions:latest/obj\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8049b6dd", + "metadata": {}, + "outputs": [], + "source": [ + "weave.legacy.weave.panels.Board(\n", + " vars={\n", + " #'all_preds': predictions,\n", + " 'x_max': weave.legacy.weave.ops.date_parse(str(end_date)),\n", + " 'x_min': lambda x_max: x_max - weave.legacy.weave.ops.days(5),\n", + " 'predictions_query': weave.legacy.weave.panels.Query(\n", + " predictions,\n", + " conditions=[\n", + " lambda query_input: weave.legacy.weave.panels.QueryCondition(\n", + " expression=query_input['username'],\n", + " editor=weave.legacy.weave.panels.SelectEditor(\n", + " choices=query_input['username']\n", + " )\n", + " )\n", + " ]),\n", + " 'predictions': lambda predictions_query: predictions_query.selected()\n", + " },\n", + " panels=[\n", + " weave.legacy.weave.panels.BoardPanel(\n", + " lambda predictions, x_min, x_max: wandb.TimeSeries(\n", + " predictions,\n", + " x=lambda item: item[\"timestamp\"],\n", + " label=lambda item: item[\"model_version\"],\n", + " agg=lambda preds: preds.count(),\n", + " min_x=x_min,\n", + " max_x=x_max,\n", + " mark=\"bar\",\n", + " axis_labels={\"y\": \"num_preds\"},\n", + " ),\n", + " layout=weave.legacy.weave.panels.BoardPanelLayout(x=0, y=0, w=12, h=6)\n", + " ),\n", + " weave.legacy.weave.panels.BoardPanel(\n", + " lambda predictions, x_min, x_max: wandb.TimeSeries(\n", + " predictions,\n", + " x=lambda item: item[\"timestamp\"],\n", + " label=lambda item: item[\"username\"],\n", + " agg=lambda preds: preds.count(),\n", + " min_x=x_min,\n", + " max_x=x_max,\n", + " mark=\"bar\",\n", + " axis_labels={\"y\": \"num_preds\"},\n", + " ),\n", + " layout=weave.legacy.weave.panels.BoardPanelLayout(x=12, y=0, w=12, h=6)\n", + " ),\n", + " weave.legacy.weave.panels.BoardPanel(\n", + " lambda predictions, x_min, x_max: wandb.TimeSeries(\n", + " predictions,\n", + " x=lambda item: item[\"timestamp\"],\n", + " #label=lambda item: item[\"model_version\"],\n", + " agg=lambda preds: preds[\"api_cost\"].sum(),\n", + " min_x=x_min,\n", + " max_x=x_max,\n", + " mark=\"line\",\n", + " axis_labels={\"y\": \"api_cost\"},\n", + " ),\n", + " layout=weave.legacy.weave.panels.BoardPanelLayout(x=0, y=6, w=8, h=6)\n", + " ),\n", + " weave.legacy.weave.panels.BoardPanel(\n", + " lambda predictions, x_min, x_max: wandb.TimeSeries(\n", + " predictions,\n", + " x=lambda item: item[\"timestamp\"],\n", + " #label=lambda item: item[\"model_version\"],\n", + " agg=lambda preds: preds[\"prompt_tokens\"].sum() + preds['completion_tokens'].sum(),\n", + " min_x=x_min,\n", + " max_x=x_max,\n", + " mark=\"line\",\n", + " axis_labels={\"y\": \"total_tokens\"},\n", + " ),\n", + " layout=weave.legacy.weave.panels.BoardPanelLayout(x=8, y=6, w=8, h=6)\n", + " ),\n", + " weave.legacy.weave.panels.BoardPanel(\n", + " lambda predictions, x_min, x_max: wandb.TimeSeries(\n", + " predictions,\n", + " x=lambda item: item[\"timestamp\"],\n", + " #label=lambda item: item[\"model_version\"],\n", + " agg=lambda preds: preds[\"latency\"].avg(),\n", + " min_x=x_min,\n", + " max_x=x_max,\n", + " mark=\"line\",\n", + " axis_labels={\"y\": \"avg_latency\"},\n", + " ),\n", + " layout=weave.legacy.weave.panels.BoardPanelLayout(x=16, y=6, w=8, h=6)\n", + " ),\n", + " weave.legacy.weave.panels.BoardPanel(\n", + " lambda predictions, x_min, x_max: wandb.Distribution(\n", + " predictions,\n", + " value_fn=lambda pred: pred['api_cost'],\n", + " bin_size=0.2\n", + " ),\n", + " layout=weave.legacy.weave.panels.BoardPanelLayout(x=0, y=12, w=8, h=6)\n", + " ),\n", + " weave.legacy.weave.panels.BoardPanel(\n", + " lambda predictions, x_min, x_max: wandb.Distribution(\n", + " predictions,\n", + " value_fn=lambda pred: pred['prompt_tokens'] + pred['completion_tokens'],\n", + " bin_size=25\n", + " ),\n", + " layout=weave.legacy.weave.panels.BoardPanelLayout(x=8, y=12, w=8, h=6)\n", + " ),\n", + " weave.legacy.weave.panels.BoardPanel(\n", + " lambda predictions, x_min, x_max: wandb.Distribution(\n", + " predictions,\n", + " value_fn=lambda pred: pred['latency'],\n", + " bin_size=0.05\n", + " ),\n", + " layout=weave.legacy.weave.panels.BoardPanelLayout(x=16, y=12, w=8, h=6)\n", + " ),\n", + " weave.legacy.weave.panels.BoardPanel(\n", + " lambda predictions: predictions,\n", + " layout=weave.legacy.weave.panels.BoardPanelLayout(x=0, y=18, w=24, h=12)\n", + " )\n", + " ]\n", + ")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/weave/legacy/examples/experimental/skip_test/Multiple Y Axes.ipynb b/weave/legacy/examples/experimental/skip_test/Multiple Y Axes.ipynb new file mode 100644 index 00000000000..a3d9af12a7c --- /dev/null +++ b/weave/legacy/examples/experimental/skip_test/Multiple Y Axes.ipynb @@ -0,0 +1,184 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "0903a3ef", + "metadata": {}, + "source": [ + "## Using this notebook to develop multiple y axes on Panel Plot" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f6c524b9", + "metadata": {}, + "outputs": [], + "source": [ + "import weave\n", + "import math\n", + "import random\n", + "weave.use_frontend_devmode()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f8d46616", + "metadata": {}, + "outputs": [], + "source": [ + "# PanelPlot supports multiple Y axes. Create a table with one row per series, with lists of x and y values.\n", + "data = []\n", + "for run in ['a1', 'a2', 'b1', 'b2', 'b3', 'c1']:\n", + " for x in range(100):\n", + " data.append({\n", + " 'group': run[0],\n", + " 'run': run,\n", + " 'step': x,\n", + " 'acc': random.random(),\n", + " })\n", + " \n", + "plot = weave.legacy.weave.panels.Plot(data)\n", + "plot.set_x(lambda row: row['step'])\n", + "plot.set_y(lambda row: row['acc'])\n", + "plot.set_label(lambda row: row['run'])\n", + "weave.show(plot)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3d97c0fd", + "metadata": {}, + "outputs": [], + "source": [ + "# PanelPlot supports multiple Y axes. Create a table with one row per series, with lists of x and y values.\n", + "data = []\n", + "xs = [i / 1000.0 for i in range(0, 1000)]\n", + "for series in range(3):\n", + " data.append({\n", + " 'key': 'y%s' % series,\n", + " 'x': xs,\n", + " 'y': [math.sin(10 * x / (series+1)) for x in xs]\n", + " })\n", + "plot = weave.legacy.weave.panels.Plot(data)\n", + "plot.set_x(lambda row: row['x'])\n", + "plot.set_y(lambda row: row['y'])\n", + "plot.set_label(lambda row: row['key'])\n", + "weave.show(plot)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "00fdefb5", + "metadata": {}, + "outputs": [], + "source": [ + "# Or you can unnest the data yourself (one row per point)\n", + "data = []\n", + "xs = [i / 1000.0 for i in range(0, 1000)]\n", + "for series in range(3):\n", + " for x in xs:\n", + " data.append({\n", + " 'key': 'y%s' % series,\n", + " 'x': x,\n", + " 'y': math.sin(10 * x / (series+1))\n", + " })\n", + "plot = weave.legacy.weave.panels.Plot(data)\n", + "plot.set_x(lambda row: row['x'])\n", + "plot.set_y(lambda row: row['y'])\n", + "plot.set_label(lambda row: row['key'])\n", + "plot.set_mark('line')\n", + "weave.show(plot)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5f9b0741", + "metadata": {}, + "outputs": [], + "source": [ + "# If your series are column-oriented, you can use df.melt to orient the data to work with PanelPlot\n", + "\n", + "import pandas as pd\n", + "df = pd.DataFrame()\n", + "df['x'] = [i / 1000.0 for i in range(0, 1000)]\n", + "df['y1'] = df['x'].map(lambda x: math.sin(10 * x / 1))\n", + "df['y2'] = df['x'].map(lambda x: math.sin(10 * x / 2))\n", + "df['y3'] = df['x'].map(lambda x: math.sin(10 * x / 3))\n", + "\n", + "# df.melt converts to a table with three columns: x, series, y\n", + "df = df.melt(id_vars=['x'], value_vars=['y1', 'y2', 'y3'], var_name='series', value_name='y')\n", + "\n", + "multiple_y = df.to_dict('records')\n", + "plot = weave.legacy.weave.panels.Plot(multiple_y)\n", + "plot.set_x(lambda row: row['x'])\n", + "plot.set_y(lambda row: row['y'])\n", + "plot.set_label(lambda row: row['series'])\n", + "plot.set_mark('line')\n", + "weave.show(plot)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "07752656", + "metadata": {}, + "outputs": [], + "source": [ + "# Or, leave the table in columnar form, and return arrays of the values you want for each series for PanelPlot\n", + "# x and label dimensions.\n", + "\n", + "# This relies on changes in this branch.\n", + "\n", + "# Note ExpressionEditor doesn't render list literals so it looks weird in the panel config. And we'll probably want\n", + "# to make the config editor more helpful, so that you choose labels along-side each y axis choice, or so that\n", + "# if you return a dict for Y we use the keys as the labels.\n", + "\n", + "import pandas as pd\n", + "df = pd.DataFrame()\n", + "df['x'] = [i / 100.0 for i in range(0, 100)]\n", + "df['x1'] = [i / 100.0 for i in range(0, 100)]\n", + "df['x2'] = [2 * i / 100.0 for i in range(0, 100)]\n", + "df['x3'] = [3 * i / 100.0 for i in range(0, 100)]\n", + "df['y1'] = df['x'].map(lambda x: math.sin(10 * x / 1))\n", + "df['y2'] = df['x'].map(lambda x: math.sin(10 * x / 2))\n", + "df['y3'] = df['x'].map(lambda x: math.sin(10 * x / 3))\n", + "multiple_y = df.to_dict('records')\n", + "\n", + "plot = weave.legacy.weave.panels.Plot(multiple_y)\n", + "plot.set_x(lambda row: row['x'])\n", + "\n", + "# Return lists for y and label\n", + "plot.set_y(lambda row: [row['y1'], row['y2'], row['y3']])\n", + "plot.set_label(lambda row: ['y1', 'y2', 'y3'])\n", + "plot.set_mark('line')\n", + "weave.show(plot)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/weave/legacy/examples/experimental/skip_test/Mutation - Code Editor.ipynb b/weave/legacy/examples/experimental/skip_test/Mutation - Code Editor.ipynb new file mode 100644 index 00000000000..937f2c0a27c --- /dev/null +++ b/weave/legacy/examples/experimental/skip_test/Mutation - Code Editor.ipynb @@ -0,0 +1,55 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "b856b674", + "metadata": {}, + "outputs": [], + "source": [ + "import weave\n", + "weave.use_frontend_devmode()\n", + "weave.capture_weave_server_logs()" + ] + }, + { + "cell_type": "markdown", + "id": "e056ac84", + "metadata": {}, + "source": [ + "Run the cell below, then click the gear icon to change to TextEditor. Your changes will be reflected in train.py" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c71000cc", + "metadata": {}, + "outputs": [], + "source": [ + "weave.legacy.weave.ops.local_path('../weave/testdata/train.py')" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/weave/legacy/examples/experimental/skip_test/Mutation.ipynb b/weave/legacy/examples/experimental/skip_test/Mutation.ipynb new file mode 100644 index 00000000000..454d25c6e5f --- /dev/null +++ b/weave/legacy/examples/experimental/skip_test/Mutation.ipynb @@ -0,0 +1,54 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "b856b674", + "metadata": {}, + "outputs": [], + "source": [ + "import weave\n", + "weave.capture_weave_server_logs()" + ] + }, + { + "cell_type": "markdown", + "id": "e056ac84", + "metadata": {}, + "source": [ + "Change the \"name\" column in the panel below to StringEditor, then click a cell to edit the underlying csv." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c71000cc", + "metadata": {}, + "outputs": [], + "source": [ + "weave.legacy.weave.ops.local_path('../weave/testdata/cereal.csv').readcsv" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/experimental/skip_test/Object browsing.ipynb b/weave/legacy/examples/experimental/skip_test/Object browsing.ipynb similarity index 100% rename from examples/experimental/skip_test/Object browsing.ipynb rename to weave/legacy/examples/experimental/skip_test/Object browsing.ipynb diff --git a/weave/legacy/examples/experimental/skip_test/OpenAI.ipynb b/weave/legacy/examples/experimental/skip_test/OpenAI.ipynb new file mode 100644 index 00000000000..dae4d5b5262 --- /dev/null +++ b/weave/legacy/examples/experimental/skip_test/OpenAI.ipynb @@ -0,0 +1,106 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "94f2344d", + "metadata": {}, + "outputs": [], + "source": [ + "import weave\n", + "from weave.legacy.weave.ecosystem import openai" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1abed8ce", + "metadata": {}, + "outputs": [], + "source": [ + "# Construct a dataset, we'll see if GPT-3 can do simple arithmetic\n", + "data = []\n", + "for i in range (101):\n", + " a = i\n", + " b = i % 9\n", + " r = a + b\n", + " data.append({'id': i, 'prompt': '%s + %s =' % (a, b), 'completion': ' %s' % r})\n", + "data = weave.save(data, name='openai-dataset')\n", + "data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "348d63f0", + "metadata": {}, + "outputs": [], + "source": [ + "# Fine tune gpt-3 on our dataset.\n", + "\n", + "# Change this to \"openai.finetune_gpt3\" to run a real OpenAI fine-tune job\n", + "fine_tune = openai.finetune_gpt3_demo(data, {'n_epochs' : 2})\n", + "fine_tune" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fe3d0872", + "metadata": {}, + "outputs": [], + "source": [ + "model = fine_tune.model()\n", + "model = weave.save(model, name='openai-model')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7d656075", + "metadata": {}, + "outputs": [], + "source": [ + "# Show all the models we've trained, and \n", + "for version in weave.versions(model):\n", + " print(weave.expr(version))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "89bebbff", + "metadata": {}, + "outputs": [], + "source": [ + "# make some predictions using our new model\n", + "\n", + "panel = weave.legacy.weave.panels.Table(['1 + 9 =', '2 + 14 ='])\n", + "panel.table_query.add_column(lambda row: row)\n", + "panel.table_query.add_column(lambda row: model.complete(row)['choices'][0]['text'])\n", + "weave.show(panel)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/weave/legacy/examples/experimental/skip_test/Oxford-IIIT Pet Dataset.ipynb b/weave/legacy/examples/experimental/skip_test/Oxford-IIIT Pet Dataset.ipynb new file mode 100644 index 00000000000..5b84ccbda13 --- /dev/null +++ b/weave/legacy/examples/experimental/skip_test/Oxford-IIIT Pet Dataset.ipynb @@ -0,0 +1,115 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "2cfb0934", + "metadata": {}, + "outputs": [], + "source": [ + "import weave\n", + "weave.use_frontend_devmode()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b81fda7c", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "im_names = os.listdir('/Users/shawn/datasets/oxford-iiit-pet-dataset/images')\n", + "breeds = set(i.rsplit('_', 1)[0] for i in im_names)\n", + "#sorted(breeds)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5e3925df", + "metadata": {}, + "outputs": [], + "source": [ + "import typing\n", + "type(typing.Optional[int])\n", + "type(typing._UnionGenericAlias)\n", + "# isinstance(typing.Optional, typing._SpecialForm)\n", + "# typing._SpecialForm.__class__" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9d20c10d", + "metadata": {}, + "outputs": [], + "source": [ + "#weave.legacy.weave.ops.local_path('/Users/shawn/datasets')\n", + "# :(, broken right now" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7f668d48", + "metadata": {}, + "outputs": [], + "source": [ + "from weave.legacy.weave.ecosystem.shawn import petdataset\n", + "\n", + "data = petdataset.petdataset('/Users/shawn/datasets/oxford-iiit-pet-dataset')\n", + "#data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "981fc71f", + "metadata": {}, + "outputs": [], + "source": [ + "from weave.legacy.weave.ecosystem import wandb\n", + "wandb.MultiDistribution(data, value_fn=lambda item: item['breed'])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b123d4f9", + "metadata": {}, + "outputs": [], + "source": [ + "# A PanelPlot only histogram\n", + "plot = weave.legacy.weave.panels.Plot(\n", + " data,\n", + " y=lambda item: item['breed'],\n", + " groupby_y=True,\n", + " x=lambda group: group.count(),\n", + " mark='bar')\n", + "#plot" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/weave/legacy/examples/experimental/skip_test/Performance profiling.ipynb b/weave/legacy/examples/experimental/skip_test/Performance profiling.ipynb new file mode 100644 index 00000000000..c69c3f119d6 --- /dev/null +++ b/weave/legacy/examples/experimental/skip_test/Performance profiling.ipynb @@ -0,0 +1,153 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "c87095b6", + "metadata": {}, + "outputs": [], + "source": [ + "import weave\n", + "import numpy as np\n", + "import pyarrow as pa\n", + "import pyarrow.parquet as pq\n", + "\n", + "!rm -rf /tmp/local-artifacts\n", + "\n", + "t = pa.table({d: list(range(400)) * 1000 for d in \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\"})\n", + "weave_t = weave.legacy.weave.ops.ArrowWeaveList(t)\n", + "weave.use(weave_t.groupby(lambda x: x['A']).map(lambda row: row.key()))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3a7726e4", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import cProfile\n", + "import weave\n", + "from weave.legacy.weave import storage\n", + "import random\n", + "import time\n", + "import pyarrow as pa\n", + "from PIL import Image\n", + "\n", + "def create_arrow_data(n_rows):\n", + " inner_count = int(n_rows / 25)\n", + " rotates = []\n", + " shears = []\n", + " x = []\n", + " y = []\n", + " random.seed(0)\n", + " for rotate in range(5):\n", + " for shear in range(5):\n", + " for i in range(inner_count):\n", + " rotates.append(rotate)\n", + " shears.append(shear)\n", + " x.append(random.choice(['a', 'b', 'c']))\n", + " y.append(random.randrange(10))\n", + " table = pa.table({\n", + " 'rotate': rotates,\n", + " 'shear': shears,\n", + " 'x': x,\n", + " 'y': y,\n", + " })\n", + " table_list = weave.legacy.weave.ops.ArrowTableList(table)\n", + "\n", + " return storage.save(table_list)\n", + "\n", + "def create_data(n_rows):\n", + " inner_count = int(n_rows / 25)\n", + " ims = []\n", + " base_im = Image.linear_gradient(\"L\")\n", + " random.seed(0)\n", + " for rotate in range(5):\n", + " for shear in range(5):\n", + " for i in range(inner_count):\n", + " ims.append(\n", + " {\n", + " \"rotate\": rotate,\n", + " \"shear\": shear,\n", + " \"y\": random.choice([\"a\", \"b\", \"c\"]),\n", + " \"x\": random.randrange(10),\n", + "# \"image\": base_im.rotate(rotate * 4).transform(\n", + "# (256, 256),\n", + "# Image.AFFINE,\n", + "# (1, shear / 10, 0, 0, 1, 0),\n", + "# Image.BICUBIC,\n", + "# ),\n", + " }\n", + " )\n", + "\n", + " return storage.save(ims)\n", + "\n", + "\n", + "def run(ref):\n", + "# node = (\n", + "# weave.get(ref)\n", + "# .groupby(lambda row: weave.legacy.weave.ops.dict_(rotate=row[\"rotate\"], shear=row[\"shear\"]))\n", + "# .map(lambda row: row.groupby(lambda row: row[\"y\"]))\n", + "# .dropna()\n", + "# .count()\n", + "# )\n", + " node = (\n", + " weave.get(ref)\n", + " # .map(lambda row: row['rotate'] + 1)\n", + " # .map(lambda row: row + 9)\n", + " .groupby(lambda row: weave.legacy.weave.ops.dict_(rotate=row[\"rotate\"], shear=row[\"shear\"]))\n", + " [1]\n", + " .count()\n", + " )\n", + " result = weave.use(node)\n", + " print('Run result: %s' % result)\n", + "\n", + "\n", + "os.system(\"rm -rf /tmp/local-artifacts/*\")\n", + "ref = create_data(100000)\n", + "print(\"Data ref: \", ref)\n", + "start_time = time.time()\n", + "cProfile.run('run(\"%s\")' % ref, \"profile_stats\")\n", + "print('Run time: %ss' % (time.time() - start_time))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "44f3f7ba", + "metadata": {}, + "outputs": [], + "source": [ + "import pstats\n", + "from pstats import SortKey\n", + "p = pstats.Stats('profile_stats')\n", + "#p.strip_dirs().sort_stats(SortKey.CUMULATIVE).print_stats()\n", + "p.strip_dirs().sort_stats(SortKey.CUMULATIVE).print_callers('type_of')\n", + "#p.strip_dirs().sort_stats(SortKey.TIME).print_stats()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/weave/legacy/examples/experimental/skip_test/ProdMonStreamTableIntro.ipynb b/weave/legacy/examples/experimental/skip_test/ProdMonStreamTableIntro.ipynb new file mode 100644 index 00000000000..f8233308156 --- /dev/null +++ b/weave/legacy/examples/experimental/skip_test/ProdMonStreamTableIntro.ipynb @@ -0,0 +1,135 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "9617c234", + "metadata": {}, + "source": [ + "# StreamTable API" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "307ad3b7", + "metadata": {}, + "outputs": [], + "source": [ + "import weave\n", + "weave.use_frontend_devmode()\n", + "from weave.legacy.weave.monitoring import StreamTable\n", + "import PIL\n", + "import numpy as np\n", + "\n", + "def make_image():\n", + " imarray = np.random.rand(100, 100, 3) * 255\n", + " return PIL.Image.fromarray(imarray.astype(\"uint8\")).convert(\"RGBA\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8c7b6c63", + "metadata": {}, + "outputs": [], + "source": [ + "table = StreamTable(\"pm_demo_all_2\", entity_name=\"timssweeney\", project_name=\"prodmon\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "643f46e3", + "metadata": {}, + "outputs": [], + "source": [ + "for _ in range(10):\n", + " table.log({\n", + " 'number': 1, \n", + " 'string': 'hello', \n", + " 'bool': True, \n", + " 'nested': {'data': 5}, \n", + " 'lists': list(range(10)), \n", + " 'custom': make_image()})\n", + "table" + ] + }, + { + "cell_type": "markdown", + "id": "56c50afb", + "metadata": {}, + "source": [ + "# Monitor Decorator" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e7db9358", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain.agents import load_tools\n", + "from langchain.agents import initialize_agent\n", + "from langchain.agents import AgentType\n", + "from langchain.llms import OpenAI\n", + "\n", + "llm = OpenAI(temperature=0)\n", + "tools = load_tools([\"llm-math\"], llm=llm)\n", + "agent = initialize_agent(tools, llm, agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "90aba081", + "metadata": {}, + "outputs": [], + "source": [ + "from weave.legacy.weave.monitoring import monitor\n", + "\n", + "@monitor(entity_name=\"timssweeney\", project_name=\"prodmon\")\n", + "def ask_llm_calculator(prompt, question):\n", + " return agent.run(prompt + \" \" + question)\n", + "\n", + "\n", + "ask_llm_calculator(\n", + " \"Please accurately answer the following question:\", \n", + " \"Find the square root of 5.4.\"\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f49b9b31", + "metadata": {}, + "outputs": [], + "source": [ + "# This API will change\n", + "ask_llm_calculator._stream_table._stream_table.rows()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/weave/legacy/examples/experimental/skip_test/Shap.ipynb b/weave/legacy/examples/experimental/skip_test/Shap.ipynb new file mode 100644 index 00000000000..5ca6196df6d --- /dev/null +++ b/weave/legacy/examples/experimental/skip_test/Shap.ipynb @@ -0,0 +1,60 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "eb58c451", + "metadata": {}, + "outputs": [], + "source": [ + "import weave\n", + "from weave.legacy.weave.ecosystem import shap" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bac8abef", + "metadata": {}, + "outputs": [], + "source": [ + "dataset = shap.ca_housing_dataset(1)\n", + "xy = shap.split_labels(dataset, 'MedHouseVal')\n", + "model = shap.xgboost_train(xy, {'learning_rate': 0.02})\n", + "\n", + "explain_res = model.shap_explain(xy['X'])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "aa726585", + "metadata": {}, + "outputs": [], + "source": [ + "explain_res.summary_plot()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/weave/legacy/examples/experimental/skip_test/StreamTables.ipynb b/weave/legacy/examples/experimental/skip_test/StreamTables.ipynb new file mode 100644 index 00000000000..78e5331bec8 --- /dev/null +++ b/weave/legacy/examples/experimental/skip_test/StreamTables.ipynb @@ -0,0 +1,92 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "1333d291", + "metadata": {}, + "outputs": [], + "source": [ + "import weave\n", + "weave.use_frontend_devmode()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "17a80a08", + "metadata": {}, + "outputs": [], + "source": [ + "from weave.legacy.weave.wandb_interface.wandb_stream_table import StreamTable" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "058fa5af", + "metadata": {}, + "outputs": [], + "source": [ + "import time\n", + "entity_name = \"timssweeney\"\n", + "project_name = \"stream_tables\"\n", + "run_name = \"tt\" + str(int(time.time()))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f0691851", + "metadata": {}, + "outputs": [], + "source": [ + "st = StreamTable(run_name, project_name, entity_name)\n", + "\n", + "for i in range(10):\n", + " st.log({'a': i})" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e0a597a5", + "metadata": {}, + "outputs": [], + "source": [ + "st" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fb42732f", + "metadata": {}, + "outputs": [], + "source": [ + "weave.legacy.weave.ops.project(entity_name, project_name).run(run_name).history2()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/weave/legacy/examples/experimental/skip_test/TimeSeries.ipynb b/weave/legacy/examples/experimental/skip_test/TimeSeries.ipynb new file mode 100644 index 00000000000..73e03f7079a --- /dev/null +++ b/weave/legacy/examples/experimental/skip_test/TimeSeries.ipynb @@ -0,0 +1,148 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "e4885744", + "metadata": {}, + "outputs": [], + "source": [ + "import weave\n", + "weave.use_frontend_devmode()\n", + "from weave.legacy.weave import ops_arrow\n", + "from weave.legacy.weave.ecosystem import wandb" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "50e46344", + "metadata": {}, + "outputs": [], + "source": [ + "import datetime\n", + "import random\n", + "random.seed(1)\n", + "\n", + "min_x = datetime.datetime(2023, 3, 22, 11, 0, 0)\n", + "max_x = min_x + datetime.timedelta(hours=1)\n", + "\n", + "users = ['nick', 'shawn', 'stacey', 'tim', 'danny']\n", + "model_versions = ['v1', 'v2']\n", + "\n", + "num_requests = 10000\n", + "\n", + "def random_datetime_in_range() -> datetime.datetime:\n", + " min_timestamp = min_x.timestamp()\n", + " max_timestamp = max_x.timestamp()\n", + " random_timestamp = random.uniform(min_timestamp, max_timestamp)\n", + " return datetime.datetime.fromtimestamp(random_timestamp)\n", + "\n", + "def random_suggestion(n: int) -> str:\n", + " result = ''\n", + " for i in range(n):\n", + " result += random.choice('abcdefghijklmnopqrstuvwxyz')\n", + " return result\n", + "\n", + "\n", + "items = weave.save(\n", + " ops_arrow.to_arrow([\n", + " {\n", + " \"pred\": random_suggestion(7),\n", + " \"time\": random_datetime_in_range(),\n", + " \"user\": random.choice(users),\n", + " \"version\": random.choice(model_versions),\n", + " \"accepted\": random.choice([1, 1, 1, 0, 0]),\n", + " } \n", + " for _ in range(num_requests)\n", + " ])\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fb863d87", + "metadata": {}, + "outputs": [], + "source": [ + "panel = weave.legacy.weave.panels.Group(\n", + " items={\n", + " \"num_preds\": weave.legacy.weave.panels.LabeledItem(\n", + " label=\"Predictions by version over time\",\n", + " item=wandb.TimeSeries(\n", + " items,\n", + " x=lambda item: item[\"time\"],\n", + " label=lambda item: item[\"version\"],\n", + " agg=lambda preds: preds.count(),\n", + " min_x=min_x,\n", + " max_x=max_x,\n", + " mark=\"bar\",\n", + " axis_labels={\"y\": \"num_preds\"},\n", + " ),\n", + " ),\n", + " \"bottom_row\": weave.legacy.weave.panels.Group(\n", + " style=\"height: 400px;\",\n", + " preferHorizontal=True,\n", + " items={\n", + " \"preds_by_user\": weave.legacy.weave.panels.LabeledItem(\n", + " label=\"Predictions by user over time\",\n", + " item=wandb.TimeSeries(\n", + " items,\n", + " x=lambda item: item[\"time\"],\n", + " label=lambda item: item[\"user\"],\n", + " agg=lambda preds: preds.count(),\n", + " min_x=min_x,\n", + " max_x=max_x,\n", + " mark=\"bar\",\n", + " axis_labels={\"y\": \"num_preds\"},\n", + " ),\n", + " ),\n", + " \"acceptance_rate\": weave.legacy.weave.panels.LabeledItem(\n", + " label=\"Acceptance rate over time by version\",\n", + " item=wandb.TimeSeries(\n", + " items,\n", + " x=lambda item: item[\"time\"],\n", + " label=lambda item: item[\"version\"],\n", + " agg=lambda preds: preds[\"accepted\"].sum() / preds.count(),\n", + " min_x=min_x,\n", + " max_x=max_x,\n", + " mark=\"line\",\n", + " axis_labels={\"y\": \"acceptance_rate\"},\n", + " ),\n", + " ),\n", + " },\n", + " ),\n", + " }\n", + ")\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "35c7e310", + "metadata": {}, + "outputs": [], + "source": [ + "panel" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "50b1be3e", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/weave/legacy/examples/experimental/skip_test/Untitled.ipynb b/weave/legacy/examples/experimental/skip_test/Untitled.ipynb new file mode 100644 index 00000000000..ff38e5faef8 --- /dev/null +++ b/weave/legacy/examples/experimental/skip_test/Untitled.ipynb @@ -0,0 +1,51 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "5ca8d4be", + "metadata": {}, + "outputs": [], + "source": [ + "import csv\n", + "import weave" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "492a6655", + "metadata": {}, + "outputs": [], + "source": [ + "#help(csv.reader)\n", + "# reader = csv.reader(open('/Users/shawn/Downloads/wandb_export_2023-03-30T15_21_00.493-04_00.csv'))\n", + "# for row in reader:\n", + "# print(row)\n", + "#help(csv.reader)\n", + "weave.legacy.weave.ops.local_path('/Users/shawn/code2/weave/weave/testdata/cereal.csv').readcsv()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/weave/legacy/examples/experimental/skip_test/Vectorizing.ipynb b/weave/legacy/examples/experimental/skip_test/Vectorizing.ipynb new file mode 100644 index 00000000000..488c7f42ff5 --- /dev/null +++ b/weave/legacy/examples/experimental/skip_test/Vectorizing.ipynb @@ -0,0 +1,91 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "5f10d941", + "metadata": {}, + "outputs": [], + "source": [ + "import weave\n", + "import time\n", + "from weave.legacy.weave import storage\n", + "from weave.legacy.weave.ops_primitives import geom" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9f3026bd", + "metadata": {}, + "outputs": [], + "source": [ + "segs = []\n", + "for i in range(1000000):\n", + " segs.append(geom.LineSegment(i*0.1, i+0.2, i+0.3, i*0.5))\n", + "l = weave.save(segs)\n", + "arr = weave.legacy.weave.ops.ArrowArrayList(storage.to_arrow(segs))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7544d866", + "metadata": {}, + "outputs": [], + "source": [ + "# pure python\n", + "start_time = time.time()\n", + "sum(seg.length.resolve_fn(seg) for seg in segs)\n", + "print('TIME: ', time.time() - start_time)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "11c2a9b6", + "metadata": {}, + "outputs": [], + "source": [ + "# weave list\n", + "start_time = time.time()\n", + "weave.use(l.map(lambda seg: seg.length()).sum())\n", + "print('TIME: ', time.time() - start_time)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "71816731", + "metadata": {}, + "outputs": [], + "source": [ + "# weave vectorized arrow\n", + "start_time = time.time()\n", + "weave.use(arr.map(lambda seg: seg.length()).sum())\n", + "print('TIME: ', time.time() - start_time)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/weave/legacy/examples/experimental/skip_test/WB data.ipynb b/weave/legacy/examples/experimental/skip_test/WB data.ipynb new file mode 100644 index 00000000000..a7a545ffa0a --- /dev/null +++ b/weave/legacy/examples/experimental/skip_test/WB data.ipynb @@ -0,0 +1,80 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "bb76d9d8", + "metadata": {}, + "outputs": [], + "source": [ + "import weave\n", + "weave.use_frontend_devmode()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "54243e21", + "metadata": {}, + "outputs": [], + "source": [ + "project = weave.legacy.weave.ops.project('shawn', 'fasion-sweep')\n", + "runs = project.runs()\n", + "\n", + "panel = weave.legacy.weave.panels.Group(\n", + " preferHorizontal=True,\n", + " items={\n", + " \"sidebar\": weave.legacy.weave.panels.Group(\n", + " style=\"width: 200px; padding: 16px;\",\n", + " items={\n", + " \"run\": weave.legacy.weave.panels.ObjectPicker(runs, label='run'),\n", + " }\n", + " ),\n", + " #\"main\": lambda sidebar: project.run(sidebar.config.items['run_id'].config.choice).history()\n", + " \"main\": lambda sidebar: sidebar.config.items['run'].config.choice.history()\n", + " }\n", + ")\n", + "panel" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5a771433", + "metadata": {}, + "outputs": [], + "source": [ + "project = weave.legacy.weave.ops.project('shawn', 'fasion-sweep')\n", + "run_ids = project.runs().id()\n", + "\n", + "panel = weave.legacy.weave.panels.Group(\n", + " preferHorizontal=True,\n", + " items={\n", + " \"sidebar\": weave.legacy.weave.panels.Group(\n", + " style=\"width: 200px; padding: 16px;\",\n", + " items={\n", + " \"run_id\": weave.legacy.weave.panels.ObjectPicker(run_ids, label='run_id'),\n", + " }\n", + " ),\n", + " #\"main\": lambda sidebar: project.run(sidebar.config.items['run_id'].config.choice).history()\n", + " \"main\": lambda sidebar: weave.legacy.weave.panels.Plot(\n", + " project.run(sidebar.config.items['run_id'].config.choice).history(),\n", + " x=lambda row: row['_step'],\n", + " y=lambda row: row['loss'],\n", + " )\n", + " }\n", + ")\n", + "#panel" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/weave/legacy/examples/experimental/skip_test/Weave geo data.ipynb b/weave/legacy/examples/experimental/skip_test/Weave geo data.ipynb new file mode 100644 index 00000000000..fba602aec01 --- /dev/null +++ b/weave/legacy/examples/experimental/skip_test/Weave geo data.ipynb @@ -0,0 +1,106 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "8d5be6da", + "metadata": {}, + "outputs": [], + "source": [ + "import weave\n", + "import pandas as pd\n", + "from weave.legacy.weave.ecosystem import wandb\n", + "weave.use_frontend_devmode()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "90518a5e", + "metadata": {}, + "outputs": [], + "source": [ + "data = pd.read_csv(\"/Users/shawn/code2/weave/database.csv\").to_dict(orient='records')\n", + "data = data[:1000]\n", + "weave.show(data)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f643bb3f", + "metadata": {}, + "outputs": [], + "source": [ + "#wandb.Scatter(data, x_fn=lambda item: item[\"Longitude\"], y_fn=lambda item: item[\"Latitude\"])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b94b992b", + "metadata": {}, + "outputs": [], + "source": [ + "# wandb.plotly_geo([\n", + "# {'lat': 35, 'long': 39, 'color': 0.1},\n", + "# {'lat': 19, 'long': 24, 'color': 100}])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6b31859b", + "metadata": {}, + "outputs": [], + "source": [ + "#wandb.Geo(data)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "872cb8d0", + "metadata": {}, + "outputs": [], + "source": [ + "weave.legacy.weave.panels.Group(\n", + " items={\n", + " 'geo': wandb.Geo(data,\n", + " x_fn=lambda item: item['Longitude'],\n", + " y_fn=lambda item: item[\"Latitude\"],\n", + " color_fn=lambda item: item[\"Magnitude\"]),\n", + " 'table': lambda geo: weave.legacy.weave.panels.LabeledItem(\n", + " label='Selected items',\n", + " item=weave.legacy.weave.panels.Group(\n", + " style=\"height: 400px;\",\n", + " preferHorizontal=True,\n", + " items={\n", + " 'table': geo.selected()\n", + " }))\n", + " })\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/weave/legacy/examples/experimental/skip_test/art_explore.ipynb b/weave/legacy/examples/experimental/skip_test/art_explore.ipynb new file mode 100644 index 00000000000..bf693b598ea --- /dev/null +++ b/weave/legacy/examples/experimental/skip_test/art_explore.ipynb @@ -0,0 +1,141 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "3df06c92", + "metadata": {}, + "source": [ + "# Art Exploration Dashboard\n", + "\n", + "This quick example programmatically buids a Weave Dashboard to explore and remix visual art.\n", + "* generate creative descriptions of artwork given some famous artists as reference points/inspiration\n", + "* create a Table (one column of text) of these art prompts (named \"style_riff\")\n", + "* use Stable Diffusion to generate art based on these prompts (\"riff_art\" column)\n", + "* inverse-generate a prompt which would lead to that image (and perhaps describe it better? \"art_desc\" column)\n", + "* imagine a perfect title for an artwork with that description (\"desc title\")\n", + "* finally, generate a remixed artwork for that title (\"remixed_art\"), except with more of the generated color specified in the horizontal panel (itself generated from the RGB values in the left sidebar)\n", + "\n", + "## Play with these parameters\n", + "\n", + "* the artist names and artist_id for which artist is the starting point\n", + "* the \"palette\" colors—these are the rows in the Dashboard\n", + "* the number of works (must be <= the number of colors in the palette and the number of artists\n", + "* the RGB values (0, 255)—these are combined to generate the \"color\" which is amplified in the remix art. Sometimes the color names are returned with extra words :)\n", + "* any of the prompts used to generate images or titles!\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1ef13bdf", + "metadata": {}, + "outputs": [], + "source": [ + "import weave\n", + "from weave.legacy.weave import weave_internal\n", + "from weave.legacy.weave.ecosystem.all import langchain" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c765a214", + "metadata": {}, + "outputs": [], + "source": [ + "weave.legacy.weave.panels.Board(\n", + " vars={\n", + " \"chat_model\": langchain.chat_openai(\"gpt-3.5-turbo\", 0.7),\n", + " \"artists\" : [\"René Magritte\", \"Josephine Wall\", \"Android Jones\", \"Alphonse Mucha\", \"Claude Monet\"],\n", + " \"artist_id\" : 0,\n", + " \"num_works\": 3,\n", + " \"palette\" : [\"red\", \"orange\", \"yellow\", \"green\", \"blue\", \"violet\"],\n", + " \"style_riff\": \n", + " lambda chat_model, num_works, artists, palette, artist_id:\n", + " weave.legacy.weave.ops.range(0, num_works, 1).map(\n", + " lambda row:\n", + " chat_model.predict(weave_internal.const(\"You are an expert art historian. In a sophisticated, avant-garde art museum, you see pieces in \")\n", + " + palette[row] + \" by \" + artists[artist_id]\n", + " + \", which has a fantastic contrast and synergy with the work of\")\n", + " \n", + " ),\n", + " \"riff_art\":\n", + " lambda style_riff, num_works:\n", + " weave.legacy.weave.ops.range(0, num_works, 1).map(\n", + " lambda row: style_riff[row]).stable_diffusion(),\n", + " \"art_desc\":\n", + " lambda riff_art, num_works:\n", + " weave.legacy.weave.ops.range(0, num_works, 1).map(\n", + " lambda row: riff_art[row]).img2prompt(),\n", + " \"desc_title\":\n", + " lambda art_desc, num_works, chat_model:\n", + " weave.legacy.weave.ops.range(0, num_works, 1).map(\n", + " lambda row: chat_model.predict(weave_internal.const(\"Art description: \")\n", + " + art_desc[row] + \". Perfect title:\")),\n", + " \"R\" : 168,\n", + " \"G\" : 156,\n", + " \"B\" : 234,\n", + " \"color\" : lambda chat_model, R, G, B:\n", + " chat_model.predict(weave_internal.const(\"RGB value: (\")\n", + " + R.toString() + \",\" + G.toString() + \",\" + B.toString()\n", + " + \"). Precise color name:\"),\n", + " \"remixed_art\":\n", + " lambda desc_title, num_works, color:\n", + " weave.legacy.weave.ops.range(0, num_works, 1).map(\n", + " lambda row: weave_internal.const(\"a beautiful painting titled \")\n", + " + desc_title[row] + \" with more \" + color).stable_diffusion()\n", + " \n", + " },\n", + " panels=[\n", + " weave.legacy.weave.panels.BoardPanel(\n", + " lambda style_riff: weave.legacy.weave.panels.Table(style_riff, columns=[lambda col: col]),\n", + " layout=weave.legacy.weave.panels.BoardPanelLayout(x=0, y=0, w=4, h=16)\n", + " ),\n", + " weave.legacy.weave.panels.BoardPanel(\n", + " lambda riff_art: weave.legacy.weave.panels.Table(riff_art, columns=[lambda col: col]),\n", + " layout=weave.legacy.weave.panels.BoardPanelLayout(x=4, y=0, w=6, h=16)\n", + " ),\n", + " weave.legacy.weave.panels.BoardPanel(\n", + " lambda art_desc: weave.legacy.weave.panels.Table(art_desc, columns=[lambda col: col]),\n", + " layout=weave.legacy.weave.panels.BoardPanelLayout(x=10, y=0, w=4, h=16)\n", + " ),\n", + " weave.legacy.weave.panels.BoardPanel(\n", + " lambda desc_title: weave.legacy.weave.panels.Table(desc_title, columns=[lambda col: col]),\n", + " layout=weave.legacy.weave.panels.BoardPanelLayout(x=14, y=0, w=4, h=16)\n", + " ),\n", + " weave.legacy.weave.panels.BoardPanel(\n", + " lambda remixed_art: weave.legacy.weave.panels.Table(remixed_art, columns=[lambda col: col]),\n", + " layout=weave.legacy.weave.panels.BoardPanelLayout(x=18, y=0, w=6, h=16)\n", + " ),\n", + " weave.legacy.weave.panels.BoardPanel(\n", + " lambda color: weave.legacy.weave.panels.StringEditor(color),\n", + " layout=weave.legacy.weave.panels.BoardPanelLayout(x=7, y=16, w=10, h=4)\n", + " ),\n", + " \n", + " ]\n", + ")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/experimental/skip_test/assets/op_preview_via_cursor.png b/weave/legacy/examples/experimental/skip_test/assets/op_preview_via_cursor.png similarity index 100% rename from examples/experimental/skip_test/assets/op_preview_via_cursor.png rename to weave/legacy/examples/experimental/skip_test/assets/op_preview_via_cursor.png diff --git a/weave/legacy/examples/experimental/skip_test/branching_runs.ipynb b/weave/legacy/examples/experimental/skip_test/branching_runs.ipynb new file mode 100644 index 00000000000..ac3106dfdc9 --- /dev/null +++ b/weave/legacy/examples/experimental/skip_test/branching_runs.ipynb @@ -0,0 +1,215 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "930f7487", + "metadata": {}, + "outputs": [], + "source": [ + "import time\n", + "import weave\n", + "from weave.legacy.weave.ops_primitives import ArrowWeaveList\n", + "from weave.legacy.weave.ops_domain import RunSegment\n", + "from weave.legacy.weave import storage\n", + "from weave import publish, type_of\n", + "from weave.legacy.weave.weave_types import List\n", + "import typing\n", + "import time\n", + "import sys\n", + "import numpy as np\n", + "from weave.legacy.weave.ops import to_arrow\n", + "\n", + "import logging\n", + "import pyarrow as pa\n", + "\n", + "\n", + "logger = logging.getLogger(\"run_segment\")\n", + "handler = logging.StreamHandler(stream=sys.stdout)\n", + "handler.setFormatter(logging.Formatter(\"%(asctime)s - %(message)s\"))\n", + "logger.addHandler(handler)\n", + "\n", + "# set to logging.INFO for more verbose profiling\n", + "logger.setLevel(logging.ERROR)\n", + "\n", + "# serializer = publish # uses w&b artifacts intead of local artifacts\n", + "serializer = storage.save\n", + "\n", + "N_NUMERIC_METRICS = 99 # number of numerical columns in the metrics table\n", + "\n", + "\n", + "def random_metrics(n: int = 10, starting_step: int = 0, delta_step: int = 1):\n", + " \"\"\"Create an array of metrics of length n starting from step starting_index.\"\"\"\n", + " if n <= 0:\n", + " raise ValueError(\"n must be at least 1\")\n", + " if starting_step < 0:\n", + " raise ValueError(\"starting index must be at least 0\")\n", + " if delta_step < 1:\n", + " raise ValueError(\"delta_step must be an integer greater than or equal to 1.\")\n", + " data = {\n", + " \"step\": np.arange(starting_step, starting_step + n * delta_step, delta_step),\n", + " \"string_col\": np.random.choice(list(\"ABCDEFGHIJKLMNOPQRSTUVWXYZ\"), n),\n", + " }\n", + " for j in range(N_NUMERIC_METRICS):\n", + " data[f\"metric{j}\"] = np.random.random(n) + np.sin(data['step'] * np.pi / 1e5)\n", + " data[f\"metric{j}\"][n // 10] = 10.\n", + " \n", + " return ArrowWeaveList(pa.table(data))\n", + "\n", + "\n", + "def create_branch(\n", + " name: str,\n", + " previous_segment: typing.Optional[RunSegment] = None,\n", + " length=10,\n", + " previous_segment_branch_frac=0.8,\n", + ") -> RunSegment:\n", + " \"\"\"Create a new segment and optionally attach it to a previous segment.\n", + "\n", + " Parameters\n", + " ----------\n", + " name: str\n", + " The name of the segment.\n", + " previous_segment: Optional[RunSegment], default None.\n", + " The parent run segment. If this is a root run segment, use None.\n", + " length: int, default = 10\n", + " The number of history rows to generate for the segment.\n", + " previous_segment_branch_frac: float satisfying 0 < branch_frac <= 1.\n", + " Parameter describing where in the previous segment to set the branch point.\n", + " A previous_segment_branch_frac of 0 sets the branch point at the previous\n", + " segment's root, whereas a previous_segment_branch_frac of 1 sets the branch\n", + " point at the end of the previous segment. A previous_segment_branch_frac of\n", + " 0.5 would include half of the previous segment's metric rows.\n", + "\n", + " Returns\n", + " -------\n", + " segment: RunSegment\n", + " The new segment.\n", + " \"\"\"\n", + " if not (0 < previous_segment_branch_frac <= 1):\n", + " raise ValueError(\"branch_frac must satisfy 0 < branch_frac <= 1\")\n", + "\n", + " if length <= 0:\n", + " raise ValueError(\"Length must be greater than 0.\")\n", + "\n", + " if previous_segment:\n", + " previous_metrics = previous_segment.metrics\n", + " n_previous_metrics = len(previous_metrics)\n", + " if n_previous_metrics > 0:\n", + " previous_segment_branch_index = (\n", + " int(previous_segment_branch_frac * n_previous_metrics) - 1\n", + " )\n", + "\n", + " # this run segment has a different root than the previous one\n", + " if previous_segment_branch_index < 0:\n", + " raise ValueError(\n", + " f\"Invalid branch point on RunSegment: previous_segment_branch_index \"\n", + " f\"{previous_segment_branch_index} must be between 0 and {len(previous_metrics) - 1}\"\n", + " )\n", + "\n", + " previous_segment_branch_step = (\n", + " previous_metrics._index(0)[\"step\"] + previous_segment_branch_index\n", + " )\n", + "\n", + " ref = storage.save(previous_segment)\n", + " new_metrics = random_metrics(\n", + " n=length, starting_step=previous_segment_branch_step + 1\n", + " )\n", + "\n", + " return RunSegment(name, ref.uri, previous_segment_branch_index, new_metrics)\n", + " return RunSegment(name, None, 0, random_metrics(length, 0))\n", + "\n", + "\n", + "def create_experiment(\n", + " num_steps: int, num_runs: int, branch_frac: float = 0.8\n", + ") -> typing.Optional[RunSegment]:\n", + " num_steps_per_run = num_steps // num_runs\n", + " segment = None\n", + " for i in range(num_runs):\n", + " segment = create_branch(\n", + " f\"branch {i}\",\n", + " segment,\n", + " length=num_steps_per_run,\n", + " previous_segment_branch_frac=branch_frac,\n", + " )\n", + " return segment\n", + "\n", + "last_segment = create_experiment(1000000, 100, 0.8)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "139bc4d0", + "metadata": {}, + "outputs": [], + "source": [ + "weave.show(last_segment)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2ca43fb6", + "metadata": {}, + "outputs": [], + "source": [ + "weave.show(last_segment.experiment())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5d8b6719", + "metadata": {}, + "outputs": [], + "source": [ + "plot = weave.legacy.weave.panels.Plot(last_segment.experiment())\n", + "series = plot.series[-1]\n", + "series2 = series.clone()\n", + "\n", + "plot.add_series(series2)\n", + "plot.set_x(lambda row: weave.legacy.weave.ops.number_bin(row['step'], weave.legacy.weave.ops.numbers_bins_equal([1, 2000], 2)))\n", + "\n", + "series.set_y(lambda row: weave.legacy.weave.ops.numbers_avg(row['metric0']))\n", + "series.set_mark_constant('line')\n", + "\n", + "series2.set_y(lambda row: weave.legacy.weave.ops.numbers_min(row['metric0']))\n", + "series2.set_y2(lambda row: weave.legacy.weave.ops.numbers_max(row['metric0']))\n", + "series2.set_mark_constant('area')\n", + "\n", + "plot.groupby_x()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ee4656e4", + "metadata": {}, + "outputs": [], + "source": [ + "plot" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/experimental/skip_test/evaluation notebook.ipynb b/weave/legacy/examples/experimental/skip_test/evaluation notebook.ipynb similarity index 100% rename from examples/experimental/skip_test/evaluation notebook.ipynb rename to weave/legacy/examples/experimental/skip_test/evaluation notebook.ipynb diff --git a/weave/legacy/examples/experimental/skip_test/image_gen_replicate.ipynb b/weave/legacy/examples/experimental/skip_test/image_gen_replicate.ipynb new file mode 100644 index 00000000000..8d4c05d0b14 --- /dev/null +++ b/weave/legacy/examples/experimental/skip_test/image_gen_replicate.ipynb @@ -0,0 +1,77 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "a1d79cb9", + "metadata": {}, + "source": [ + "# Image Generation with Replicate\n", + "\n", + "Convert a text prompt to an image and back to text with img2prompt or clip captioning." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7c49c962", + "metadata": {}, + "outputs": [], + "source": [ + "# weave-test-skip-all: replicate API is slow and fails sometimes\n", + "import os\n", + "from weave.legacy.weave.ecosystem import replicate as weave_replicate" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "38f878f2", + "metadata": {}, + "outputs": [], + "source": [ + "weave_replicate.stable_diffusion(\"Your life is a sheep herd. You're the driver.\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6e848393", + "metadata": {}, + "outputs": [], + "source": [ + "weave_replicate.img2prompt(weave_replicate.stable_diffusion(\"Your life is a sheep herd. You're the driver.\"))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "50596314", + "metadata": {}, + "outputs": [], + "source": [ + "weave_replicate.clip_prefix_caption(weave_replicate.stable_diffusion(\"Your life is a sheep herd. You're the driver.\"))" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/weave/legacy/examples/experimental/skip_test/langchain docbot.ipynb b/weave/legacy/examples/experimental/skip_test/langchain docbot.ipynb new file mode 100644 index 00000000000..ef4eeb10d91 --- /dev/null +++ b/weave/legacy/examples/experimental/skip_test/langchain docbot.ipynb @@ -0,0 +1,89 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "0d6220cd", + "metadata": {}, + "outputs": [], + "source": [ + "import weave\n", + "from weave import trace\n", + "weave.use_frontend_devmode()\n", + "from weave.legacy.weave.ecosystem.all import langchain" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a674fd63", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain.chat_models import ChatOpenAI\n", + "#ChatOpenAI(model_name='gpt-3.5-turbo').query('hey whats up')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f8cff0cd", + "metadata": {}, + "outputs": [], + "source": [ + "weave.save([\n", + " 'how do I use weave?',\n", + " 'what products does W&B have?',\n", + " 'how do I get a sweep ID from a run?'\n", + "], 'questions')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a47df334", + "metadata": {}, + "outputs": [], + "source": [ + "from langchain.docstore.document import Document\n", + "documents = weave.legacy.weave.ops.local_path('/Users/shawn/Downloads/documents.jsonl').readjsonl().limit(1000)\n", + "docs = weave.save([Document(**d) for d in weave.use(documents)], 'documents:latest')\n", + "#docs" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "865af8ce", + "metadata": {}, + "outputs": [], + "source": [ + "db = langchain.faiss_from_documents(docs, langchain.openai_embeddings())\n", + "llm = langchain.chat_openai('gpt-3.5-turbo', 0.7)\n", + "qa = langchain.retrieval_qa_from_chain_type(llm, 'stuff', db)\n", + "qa.run('lets try again 2')" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/weave/legacy/examples/experimental/skip_test/plot_selection.ipynb b/weave/legacy/examples/experimental/skip_test/plot_selection.ipynb new file mode 100644 index 00000000000..dac241c03bc --- /dev/null +++ b/weave/legacy/examples/experimental/skip_test/plot_selection.ipynb @@ -0,0 +1,63 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "c09f7649", + "metadata": {}, + "outputs": [], + "source": [ + "import weave\n", + "# weave.use_frontend_devmode()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a6b83817", + "metadata": {}, + "outputs": [], + "source": [ + "panel = weave.legacy.weave.panels.Group(\n", + " items={\n", + " \"plot\": weave.legacy.weave.panels.Plot(weave.legacy.weave.ops.range(1, 100, 1),\n", + " x=lambda row: row,\n", + " y=lambda row: row ** 2,\n", + " ),\n", + " \"table\": lambda plot: weave.legacy.weave.panels.panel_plot.selected_rows(plot)\n", + " })\n", + "\n", + "panel" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "55f43427", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/weave/legacy/examples/experimental/skip_test/slurm.ipynb b/weave/legacy/examples/experimental/skip_test/slurm.ipynb new file mode 100644 index 00000000000..4429a7285d8 --- /dev/null +++ b/weave/legacy/examples/experimental/skip_test/slurm.ipynb @@ -0,0 +1,66 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "8817764c", + "metadata": {}, + "outputs": [], + "source": [ + "import weave\n", + "from weave.legacy.weave.ecosystem import slurm" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cda9e9a3", + "metadata": {}, + "outputs": [], + "source": [ + "#slurm.slurm('http://localhost:8899').jobs()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9bee4444", + "metadata": {}, + "outputs": [], + "source": [ + "#slurm.slurm('http://localhost:8899').nodes()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e69a2f48", + "metadata": {}, + "outputs": [], + "source": [ + "slurm.slurm('http://localhost:8899')" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/experimental/skip_test/wandbot from scratch.ipynb b/weave/legacy/examples/experimental/skip_test/wandbot from scratch.ipynb similarity index 100% rename from examples/experimental/skip_test/wandbot from scratch.ipynb rename to weave/legacy/examples/experimental/skip_test/wandbot from scratch.ipynb diff --git a/weave/legacy/examples/experimental/skip_test/weave_engine_tracing.ipynb b/weave/legacy/examples/experimental/skip_test/weave_engine_tracing.ipynb new file mode 100644 index 00000000000..f395535b7de --- /dev/null +++ b/weave/legacy/examples/experimental/skip_test/weave_engine_tracing.ipynb @@ -0,0 +1,126 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "de9a4837", + "metadata": {}, + "source": [ + "## Visualize Weave engine traces using Weave\n", + "\n", + "To produce data, run the Weave server with Datadog logging on, and WEAVE_TRACE_STREAM=//
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c62364f5", + "metadata": {}, + "outputs": [], + "source": [ + "import weave\n", + "from weave.legacy.weave.wandb_interface.wandb_stream_table import StreamTable" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8d099de8", + "metadata": {}, + "outputs": [], + "source": [ + "# set this to the StreamTable your server logs to. Or run as is to use public test data.\n", + "spans = StreamTable('shawn/weave-trace-test2/test15').rows()\n", + "\n", + "board = weave.legacy.weave.panels.Board(\n", + " vars={\n", + " 'all_spans': spans,\n", + " 'trace_id': '',\n", + " 'selected_spans': lambda trace_id, all_spans: all_spans.filter(lambda row: row['trace_id'] == trace_id)\n", + " },\n", + " panels=[\n", + " weave.legacy.weave.panels.BoardPanel(\n", + " # TODO: have to compute all columns we want here, there's no way to\n", + " # tell js or python to do auto-columns behavior.\n", + " lambda all_spans: all_spans,\n", + " id='table',\n", + " layout=weave.legacy.weave.panels.BoardPanelLayout(x=0, y=0, w=24, h=6)\n", + " ),\n", + " weave.legacy.weave.panels.BoardPanel(\n", + " # TODO: have to compute all columns we want here, there's no way to\n", + " # tell js or python to do auto-columns behavior.\n", + " lambda all_spans: weave.legacy.weave.panels.Table(\n", + " all_spans,\n", + " columns=[\n", + " weave.legacy.weave.panels.TableColumn(\n", + " lambda row: row['trace_id'],\n", + " groupby=True,\n", + " ),\n", + " weave.legacy.weave.panels.TableColumn(\n", + " lambda row: row.count(),\n", + " name='span count'\n", + " ),\n", + " weave.legacy.weave.panels.TableColumn(\n", + " # TODO: not exact, we need to add duration to get end time, but we don't have an add\n", + " # for 2 vectors yet.\n", + " lambda row: (row['start_time_ms'].max() - row['start_time_ms'].min()) / 1000,\n", + " name='duration_s'\n", + " )\n", + " ]\n", + " ),\n", + " id='traces',\n", + " layout=weave.legacy.weave.panels.BoardPanelLayout(x=0, y=0, w=24, h=6)\n", + " ),\n", + " weave.legacy.weave.panels.BoardPanel(\n", + " # TODO: have to compute all columns we want here, there's no way to\n", + " # tell js or python to do auto-columns behavior.\n", + " lambda all_spans: weave.legacy.weave.panels.Table(\n", + " all_spans,\n", + " columns=[\n", + " weave.legacy.weave.panels.TableColumn(\n", + " lambda row: row['name'],\n", + " groupby=True,\n", + " ),\n", + " lambda row: row.count(),\n", + " weave.legacy.weave.panels.TableColumn(\n", + " # TODO: not exact, we need to add duration to get end time, but we don't have an add\n", + " # for 2 vectors yet.\n", + " lambda row: (row['start_time_ms'].max() - row['start_time_ms'].min()) / 1000,\n", + " name='duration_s'\n", + " )\n", + " ]\n", + " ),\n", + " id='spans',\n", + " layout=weave.legacy.weave.panels.BoardPanelLayout(x=0, y=0, w=24, h=6)\n", + " ),\n", + " weave.legacy.weave.panels.BoardPanel(\n", + " lambda selected_spans: weave.legacy.weave.panels.Trace(selected_spans),\n", + " layout=weave.legacy.weave.panels.BoardPanelLayout(x=0, y=6, w=24, h=6)\n", + " )\n", + " ]\n", + ")\n", + "board" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/weave/legacy/examples/experimental/skip_test/weave_scifi.ipynb b/weave/legacy/examples/experimental/skip_test/weave_scifi.ipynb new file mode 100644 index 00000000000..1c6d8b02736 --- /dev/null +++ b/weave/legacy/examples/experimental/skip_test/weave_scifi.ipynb @@ -0,0 +1,80 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "3c4790a9", + "metadata": {}, + "outputs": [], + "source": [ + "import weave\n", + "from weave.legacy.weave import weave_internal\n", + "from weave.legacy.weave.ecosystem.all import langchain\n", + "weave.use_frontend_devmode()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2e9fe6dd", + "metadata": {}, + "outputs": [], + "source": [ + "weave.legacy.weave.panels.Board(\n", + " vars={\n", + " \"gpt\": langchain.chat_openai(\"gpt-3.5-turbo\", 0.7),\n", + " \"viz_style\" : [\"vintage art deco travel poster\",\n", + " \"futuristic science fiction movie\",\n", + " \"cute retro space anime\"],\n", + " \"viz_id\" : 0,\n", + " \"num_rows\": 2,\n", + " \"ml_terms\" : [\"models\", \"datasets\"],\n", + " \"prompts\": \n", + " lambda gpt, num_rows, ml_terms:\n", + " weave.legacy.weave.ops.range(0, num_rows, 1).map(\n", + " lambda row:\n", + " gpt.predict(weave_internal.const(\"You are an expert AGI architect describing the most powerful and flexible user interface for working with \")\n", + " + ml_terms[row] + \"in machine learning and AI. The three most important features for this are:\")\n", + " \n", + " ),\n", + " \"dream_view\":\n", + " lambda prompts, num_rows, viz_style, viz_id:\n", + " weave.legacy.weave.ops.range(0, num_rows, 1).map(\n", + " lambda row: weave_internal.const(\"a \") + viz_style[viz_id] + \" of a \" + prompts[row]).stable_diffusion(),\n", + " },\n", + " panels=[\n", + " weave.legacy.weave.panels.BoardPanel(\n", + " lambda prompts: weave.legacy.weave.panels.Table(prompts, columns=[lambda col: col]),\n", + " layout=weave.legacy.weave.panels.BoardPanelLayout(x=0, y=0, w=12, h=16)\n", + " ),\n", + " weave.legacy.weave.panels.BoardPanel(\n", + " lambda dream_view: weave.legacy.weave.panels.Table(dream_view, columns=[lambda col: col]),\n", + " layout=weave.legacy.weave.panels.BoardPanelLayout(x=12, y=0, w=12, h=16)\n", + " ),\n", + " ]\n", + ")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/experimental/skip_test/weave_walkthrough.ipynb b/weave/legacy/examples/experimental/skip_test/weave_walkthrough.ipynb similarity index 100% rename from examples/experimental/skip_test/weave_walkthrough.ipynb rename to weave/legacy/examples/experimental/skip_test/weave_walkthrough.ipynb diff --git a/weave/legacy/examples/experimental/spacy_demo.ipynb b/weave/legacy/examples/experimental/spacy_demo.ipynb new file mode 100644 index 00000000000..bb8b4a4560f --- /dev/null +++ b/weave/legacy/examples/experimental/spacy_demo.ipynb @@ -0,0 +1,52 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "cd35f2be", + "metadata": {}, + "outputs": [], + "source": [ + "# Run the following installation if you have not already\n", + "#!python -m spacy download en_core_web_sm" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ede77c85", + "metadata": {}, + "outputs": [], + "source": [ + "from weave.legacy.weave.ecosystem.spacy import spacy\n", + "\n", + "text = \"\"\"Welcome to the spaCy Demo.\\\n", + "We currently support the small English language model, \\\n", + "and will add support for different types in the near future!\"\"\"\n", + "\n", + "spacy(text)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/weave/legacy/examples/experimental/table_data_passing.ipynb b/weave/legacy/examples/experimental/table_data_passing.ipynb new file mode 100644 index 00000000000..66cedc7efbd --- /dev/null +++ b/weave/legacy/examples/experimental/table_data_passing.ipynb @@ -0,0 +1,82 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "c09f7649", + "metadata": {}, + "outputs": [], + "source": [ + "import weave\n", + "import weave.legacy.weave.panels\n", + "# Weave package now defaults to eager mode, but lazy mode required for this example notebook for now.\n", + "weave.use_lazy_execution()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a6b83817", + "metadata": {}, + "outputs": [], + "source": [ + "panel = weave.legacy.weave.panels.Group(\n", + " items={\n", + " \"table\": weave.legacy.weave.panels.Table(weave.legacy.weave.ops.range(1, 100, 1), \n", + " columns=[\n", + " lambda row: row,\n", + " lambda row: row ** 2,\n", + " ],\n", + " ),\n", + " \"all_rows\": lambda table: weave.legacy.weave.panels.Plot(table.all_rows(),\n", + " x=lambda row: row['c_0'],\n", + " y=lambda row: row['c_1']\n", + " ),\n", + " \"derived\": lambda table: weave.legacy.weave.panels.Group(\n", + " preferHorizontal=True,\n", + " layoutMode=\"horizontal\",\n", + " items={\n", + " \"rows\": weave.legacy.weave.panels.Group(\n", + " items={\n", + " \"pinned_rows\": weave.legacy.weave.panels.Plot(table.pinned_rows(),\n", + " x=lambda row: row['c_0'],\n", + " y=lambda row: row['c_1']\n", + " ),\n", + " \"active_row\": table.active_row(),\n", + " }\n", + " ),\n", + " \"data\": weave.legacy.weave.panels.Group(\n", + " items={\n", + " \"pinned_data\": table.pinned_data(),\n", + " \"active_data\": table.active_data(),\n", + " }\n", + " )\n", + " }) \n", + " })\n", + "\n", + "panel" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/weave/legacy/examples/experimental/tag_search.ipynb b/weave/legacy/examples/experimental/tag_search.ipynb new file mode 100644 index 00000000000..e3e7ef3de53 --- /dev/null +++ b/weave/legacy/examples/experimental/tag_search.ipynb @@ -0,0 +1,110 @@ +{ + "cells": [ + { + "attachments": {}, + "cell_type": "markdown", + "id": "550a2af8", + "metadata": {}, + "source": [ + "## An example implementation of tag search\n", + "\n", + "This shows how to achieve a UI for tag search.\n", + "\n", + "Its not ideal in these ways:\n", + "- we're missing an intersection op.\n", + "- we'll need to be sure the pattern vectorizes\n", + "- list of tag strings may not be the best representation for performance at scale\n", + "- we don't have a nice way to display list of strings as tags\n", + "- no centralized tag color control\n", + "- you should be able to click on a tag and filter the table down to that tag\n", + "- varbar construction of controls is still pretty odd\n", + "\n", + "But it works!\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9c690522", + "metadata": {}, + "outputs": [], + "source": [ + "import weave\n", + "from weave.legacy.weave import weave_internal" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cd9ad6f9", + "metadata": {}, + "outputs": [], + "source": [ + "data = weave.save([\n", + " {'x': 15, 'tags': ['a', 'b']},\n", + " {'x': 23.9, 'tags': ['b', 'c']},\n", + " {'x': -8, 'tags': ['a']}\n", + "], 'taggeddata')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "34fb2bba", + "metadata": {}, + "outputs": [], + "source": [ + "weave.legacy.weave.panels.Board(\n", + " vars={\n", + " # The strange that we need to use two variables for structuring our tag picker and its value.\n", + " 'filter_tags': [],\n", + " 'tag_picker': lambda filter_tags: weave.legacy.weave.panels.SelectEditor(\n", + " filter_tags,\n", + " choices=weave_internal.const(['a', 'b', 'c'])\n", + " ),\n", + " 'data': data,\n", + " 'filtered_data': lambda data, filter_tags: data.filter(\n", + " # We don't currently have an intersection op, so we have to write the logic for it here.\n", + " # This will probably not vectorize currently, so will be slower on large amounts of data.\n", + " lambda row: weave.legacy.weave.ops.List.concat(weave.legacy.weave.ops.make_list(a=row['tags'], b=filter_tags)).unique().count()\n", + " < (row['tags'].count() + filter_tags.count())\n", + " )\n", + " },\n", + " panels=[\n", + " weave.legacy.weave.panels.BoardPanel(\n", + " lambda filtered_data: weave.legacy.weave.panels.Table(\n", + " filtered_data,\n", + " columns=[\n", + " lambda row: row['x'],\n", + " # Use json_dumps to get a better view for now.\n", + " lambda row: row['tags'].json_dumps()\n", + " ]\n", + " )\n", + " )\n", + " ]\n", + ")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/weave/legacy/examples/get_started.ipynb b/weave/legacy/examples/get_started.ipynb new file mode 100644 index 00000000000..8b8ffbd2bbb --- /dev/null +++ b/weave/legacy/examples/get_started.ipynb @@ -0,0 +1,301 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "81c835f8", + "metadata": {}, + "source": [ + "# Get Started building with Weave\n", + "\n", + "In this tutorial, you will learn how to use Weave to build an interactive dashboard to help you explore, understand, and evaluate machine learning models.\n", + "\n", + "After this tutorials, you'll know how to build a dashboard to:\n", + "\n", + "- explore inputs, outputs & labels of your models\n", + "- calculate and plot metrics about your models\n", + "- interactively build live, connected plots to drill deeper into your data\n", + "- publish sharable interactive dashboards to enable colleagues to build on your work\n", + "\n", + "# Hello, World\n", + "\n", + "First, install `weave`:\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e45d5578", + "metadata": {}, + "outputs": [], + "source": [ + "!pip install weave -qqq" + ] + }, + { + "cell_type": "markdown", + "id": "56c791d0", + "metadata": {}, + "source": [ + "Next, we'll use Weave to explore the Iris flower dataset.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b2f76045", + "metadata": {}, + "outputs": [], + "source": [ + "from sklearn.datasets import load_iris\n", + "\n", + "def get_iris_dataset():\n", + " iris = load_iris(as_frame=True)\n", + " data = iris['data']\n", + " data['target_id'] = iris['target']\n", + " data['target_name'] = iris['target_names'][iris['target']]\n", + "\n", + " def clean_column_name(c):\n", + " return c.replace(\" (cm)\", \"\").replace(\" \", \"_\")\n", + " data.columns = [clean_column_name(c) for c in data.columns]\n", + " return data\n", + "\n", + "iris_df = get_iris_dataset()" + ] + }, + { + "cell_type": "markdown", + "id": "ae1d5797", + "metadata": {}, + "source": [ + "Now that we have a dataframe, we can display it in an interactive table by calling `weave.show`.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "84f1a891", + "metadata": {}, + "outputs": [], + "source": [ + "import weave\n", + "from weave.legacy.weave.show import show\n", + "show(iris_df)" + ] + }, + { + "cell_type": "markdown", + "id": "091192b7-ede4-423f-a78d-b06a9b877bab", + "metadata": {}, + "source": [ + "This is already a lot more powerful that the static visualization of a pandas `DataFrame` because you can page through it——and we're just getting started.\n", + "\n", + "# Building plots\n", + "\n", + "Weave can dynamically create visualizations of your data.\n", + "\n", + "- At the top of the panel, click `Table`\n", + "- You should see a dropdown, click `Plot`\n", + "\n", + "You should now see a plot of `sepal_width` versus `sepal_length`, colored by `target_name`. Weave's visualization engine uses the type of each column to choose a reasonable way to display your data. You can edit this plot by clicking the pencil icon ✏️ in the top right of the panel to open the panel editor.\n", + "\n", + "Note: You can build these plots interactively using the UI or define them programmatically in Python. In this tutorial, we'll focus on building these dashboards using the UI and keep programmatic dashboard building for later tutorials.\n" + ] + }, + { + "cell_type": "markdown", + "id": "9866379b", + "metadata": {}, + "source": [ + "!['image'](https://raw.githubusercontent.com/wandb/weave/6936cd8233f382ae3fd165480dce5832c06c2453/docs/assets/get_started_0.png)\n" + ] + }, + { + "cell_type": "markdown", + "id": "bd62be8e", + "metadata": {}, + "source": [ + "## Exercise\n", + "\n", + "- Try changing the X and Y axes\n", + "- Try changing the color\n", + "\n", + "Tip: `row[\"\"]` is a **Weave Expression**. You can edit the column name inside the quotes to show the relevant column from your data.\n", + "\n", + "# Exploring Tables\n", + "\n", + "Let's display our data as a `Table` again:\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "886c7c49-e7f8-4651-8a6a-b2a08d417dda", + "metadata": {}, + "outputs": [], + "source": [ + "show(iris_df)" + ] + }, + { + "cell_type": "markdown", + "id": "6d12837d", + "metadata": {}, + "source": [ + "You can add, remove and sort each column by using the three dots button ⋮ beside the column name.\n", + "\n", + "## Exercise\n", + "\n", + "- Try sorting by the `sepal_length` column\n", + "- Try removing the `target_id` column\n", + "\n", + "## Filtering\n", + "\n", + "You can filter tables using the funnel icon ▼ on the top left.\n", + "\n", + "For example, to filter the data to only display the `setosa` flowers:\n", + "\n", + "- Click the filter icon\n", + "- Add the expression `row[\"target_name\"] == \"setosa\"`\n", + "\n", + "# Transforming data\n", + "\n", + "When analyzing models, you often need to use your data to calculate metrics or statistics to get summaries for comparison.\n", + "\n", + "To enable this, Weave has built-in operations, or **Weave Ops**, to transform data. You can also configure the data displayed in each column as we did with the plot.\n", + "\n", + "For example, to display the sum of each examples' `sepal_width` and `sepal_length`:\n", + "\n", + "- Click a column to edit (e.g. `sepal_length`)\n", + "- Change the Cell expression to `row[\"sepal_width\"] + row[\"sepal_length\"]`\n", + "- Click anywhere outside the popover to apply the change\n", + "\n", + "# Group by columns to compare splits and calculate summary statistics\n", + "\n", + "Comparing performance across different data splits is vital to uncovering biases and edge cases. This is fast and visual in Weave using the **Group by** operation.\n", + "\n", + "For example, if we want to calculate summary statistics of each type of flower in our dataset, we can group by the `target_name` column.\n", + "\n", + "- Click the three dots button ⋮ on the `target_name` column\n", + "- Click `Group by`\n", + "\n", + "We now see that Weave has grouped our data by the `target_name` and made plots for each column. We can now use different operations on these columns because they're now _lists_ rather than individual elements.\n", + "\n", + "For example, we can calculate the average of each column by changing the cell expression to be `row[\"\"].avg` e.g. `row[\"sepal_length\"].avg`.\n", + "\n", + "## Exercise\n", + "\n", + "- Try calculating the maximum of each column\n" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "id": "0954b735", + "metadata": {}, + "source": [ + "!['image'](https://raw.githubusercontent.com/wandb/weave/4885095070704922189ba4d761029821c4e5da42/docs/assets/get_started_1.png)\n" + ] + }, + { + "cell_type": "markdown", + "id": "60acbe66", + "metadata": {}, + "source": [ + "## Drilldown with connected plots\n", + "\n", + "Finally, once you have found a split of data that is interesting, you often want to view individual elements of that data to better understand it. Weave makes it easy to connect multiple visualizations so that you can drilldown into selected data.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "531013bb", + "metadata": {}, + "outputs": [], + "source": [ + "show(iris_df)" + ] + }, + { + "cell_type": "markdown", + "id": "2519db1c", + "metadata": {}, + "source": [ + "!['image'](https://raw.githubusercontent.com/wandb/weave/4885095070704922189ba4d761029821c4e5da42/docs/assets/get_started_2.png)\n" + ] + }, + { + "cell_type": "markdown", + "id": "ddad55c3", + "metadata": {}, + "source": [ + "For example, to select data from a plot and display it in a table:\n", + "\n", + "- Add a new panel from the drawer menu on the right (1)\n", + "- Using the panel name (2), refer to the `.selected_data` from the first panel in the expression editor (3)\n", + "- Use the selection tool (4) to display that data in the drilldown table\n", + "\n", + "Tip: You can also connect `Tables` to other plots. To select a row on a table, click the row number. To use that selection, use `.active_data`.\n", + "\n", + "# Publish your dashboard and share it\n", + "\n", + "Once you have something you're happy with, you can click the arrow ↗️ from the drawer menu on the right to open it in a new tab. From there, you can click \"Publish board\" to send your creation to the cloud and share it with the world. You can also use this area to add new panels and update your dashboard.\n", + "\n", + "# What's next?\n", + "\n", + "With what you've learned, try build something on your own data. Weave works with lots of different types of data and is performant at large scales!\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e2011a09", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy\n", + "from PIL import Image\n", + "\n", + "def random_image():\n", + " imarray = numpy.random.rand(100,100,3) * 255\n", + " return Image.fromarray(imarray.astype('uint8')).convert('RGBA')\n", + "\n", + "show([{'image_col': random_image(), 'text_col': 'Hello'}, \n", + " {'image_col': random_image(), 'text_col': 'World'}]*1000)" + ] + }, + { + "cell_type": "markdown", + "id": "6db51292", + "metadata": {}, + "source": [ + "We're heads down focusing on adding templates and improving our documentation. Weave is open source, and we're very excited to merge your contributions. If you have feedback, feature requests, or bugs, you can open an issue on the Weave Github repository.\n", + "\n", + "## Not finished learning?\n", + "\n", + "Check out our [examples](https://github.com/wandb/weave/tree/master/examples) to see more tutorials, a gallery of dashboards, and reference guides to different components.\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.11" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/weave/legacy/examples/reference/WB_API.ipynb b/weave/legacy/examples/reference/WB_API.ipynb new file mode 100644 index 00000000000..d97e0eb952c --- /dev/null +++ b/weave/legacy/examples/reference/WB_API.ipynb @@ -0,0 +1,89 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "eae3e8dd", + "metadata": {}, + "outputs": [], + "source": [ + "import weave\n", + "import weave.legacy.weave.panels\n", + "import weave.legacy.weave.ops\n", + "from weave.legacy.weave.show import show" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "887d88a4", + "metadata": {}, + "outputs": [], + "source": [ + "project = weave.legacy.weave.ops.project('stacey', \"mendeleev\")\n", + "#project" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b7705fb7", + "metadata": {}, + "outputs": [], + "source": [ + "panel = weave.legacy.weave.panels.Table(weave.legacy.weave.ops.project('stacey', \"mendeleev\").artifactType('test_results').artifacts(),\n", + " columns=[\n", + " lambda art: weave.legacy.weave.ops.artifact_collection_ops.artifact_name(art), # hack to get around shadowed `name` op\n", + " lambda art: art.versions().count()\n", + " ])\n", + "show(panel)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "58b26e21", + "metadata": {}, + "outputs": [], + "source": [ + "weave.legacy.weave.ops.project('stacey', \"mendeleev\").artifactType('test_results').artifacts()[0].versions()[0].file('test_results.table.json')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "debbb5d4", + "metadata": {}, + "outputs": [], + "source": [ + "weave.legacy.weave.ops.project('shawn', \"dsviz-simple-tables\").artifactType('dataset').artifacts()[0].versions()[5].file('my-table.table.json')" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + }, + "vscode": { + "interpreter": { + "hash": "cdaa5b4f919dd2bcc1e1b23efc336c7178fbd1227e862ece13b7d448295c4271" + } + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/reference/basic_weave_types.ipynb b/weave/legacy/examples/reference/basic_weave_types.ipynb similarity index 100% rename from examples/reference/basic_weave_types.ipynb rename to weave/legacy/examples/reference/basic_weave_types.ipynb diff --git a/weave/legacy/examples/reference/confusion_matrix.ipynb b/weave/legacy/examples/reference/confusion_matrix.ipynb new file mode 100644 index 00000000000..e0305d4fd6f --- /dev/null +++ b/weave/legacy/examples/reference/confusion_matrix.ipynb @@ -0,0 +1,234 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "562cca2e-fd23-4725-9c04-416fde40aadc", + "metadata": {}, + "source": [ + "# Create an Interactive Confusion Matrix in Weave\n", + "\n", + "In this tutorial, we will set up an interactive confusion matrix Weave panel using Weave expressions and Python):\n", + "* pick an existing W&B Table of predictions\n", + "* load this data into Weave locally\n", + "* programmatically configure two Weave Panels: a Facet panel for the confusion matrix grid and an active selection panel to show the details of items in that cell of the confusion matrix\n", + "\n", + "\n", + "\n", + "# Step 0: Setup\n", + "\n", + "Install dependencies and login to W&B to save & publish your work." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8544cf72", + "metadata": {}, + "outputs": [], + "source": [ + "# if not already installed\n", + "!pip install -qqq weave" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5b86972f-d8d7-4f5a-bf5a-9a6be887978c", + "metadata": {}, + "outputs": [], + "source": [ + "import wandb\n", + "wandb.login()\n", + "import weave\n", + "import weave.legacy.weave.panels" + ] + }, + { + "cell_type": "markdown", + "id": "b35a8e31-3f51-43a9-bf89-543ed6fe62e8", + "metadata": {}, + "source": [ + "Use our public example or substitute your own W&B Tables by replacing the fields below. You can find the relevant information by navigating to a project in W&B, clicking on the \"Artifacts\" tab, and browsing using the sidebar until you find the artifact type and corresponding table file of interest (e.g. [this one used in the example](https://wandb.ai/stacey/mendeleev/artifacts/test_results/test_res_1fwmcd3q/v0/files/test_results.table.json)).\n", + "\n", + "**Note:** the index '0' after `artifacts()` and `versions()` will take the first element in those lists." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "01183f54-efa6-405a-be6a-142f75540913", + "metadata": {}, + "outputs": [], + "source": [ + "ENTITY = \"stacey\"\n", + "PROJECT = \"mendeleev\"\n", + "ARTIFACT_TYPE = \"test_results\"\n", + "TABLE_FILE = \"test_results.table.json\"" + ] + }, + { + "cell_type": "markdown", + "id": "2c910260-a5c0-4c99-bd00-1b9b95ddde54", + "metadata": {}, + "source": [ + "# Step 1: Compose Weave expression to load W&B Table\n", + "\n", + "Navigate the W&B object tree programmatically with Weave\n", + "* start with the specified W&B project\n", + "* fetch all artifacts of the specified type and take the first artifact\n", + "* fetch all the versions of that artifact and take the first one\n", + "* load the specified W&B Table file associated with that artifact version\n", + "* convert that file into a Weave table (with `.table.rows()`)\n", + "\n", + "The second cell below will display an interactive Weave Panel of the corresponding Table. You can interact with this panel in the notebook as you would with the underlying W&B Table in the cloud UI: sort, filter, group, etc. You can also expand the panel to open it in a Weave Board in order to iterate further/save and publish your work." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7f5447d2", + "metadata": {}, + "outputs": [], + "source": [ + "predictions = (weave.legacy.weave.ops.project(ENTITY, PROJECT)\n", + " .artifactType(ARTIFACT_TYPE)\n", + " .artifacts()[0]\n", + " .versions()[0]\n", + " .file(TABLE_FILE)\n", + " .table()\n", + " .rows())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e414990d", + "metadata": {}, + "outputs": [], + "source": [ + "predictions" + ] + }, + { + "cell_type": "markdown", + "id": "9ee223ef-1a97-46f6-9933-24cdb78d2488", + "metadata": {}, + "source": [ + "# Step 2: Configure a Weave Panel Group in code\n", + "\n", + "We write a function to create a group of panels in Weave:\n", + "* Group panel: this is a container to arrange a set of panels — in our case, in equal sizes horizontally\n", + "* Facet panel: this is the main 2D grid of the confusion matrix, showing the `truth` column (correct label) on the x-axis and the `guess` column (model's prediction) on the y-axis\n", + "* each cell of the Facet Panel is again a Group with two layers of panels: 1) a Number panel showing the total number of rows/model predictions in that cell of the confusion matrix, and 2) a background Color panel with the count from 1) rescaled on a blue hue, so the more saturated the blue, the more items in that cell\n", + "* a final Table panel to show the full rows/details of any selected cell from the main Facet panel." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a940dab4", + "metadata": {}, + "outputs": [], + "source": [ + "def confusion_matrix(table_data):\n", + " conf_mat = weave.legacy.weave.panels.Group(\n", + " equalSize=True,\n", + " preferHorizontal=True,\n", + " items={\n", + " 'confusion': weave.legacy.weave.panels.Facet(\n", + " table_data,\n", + " x=lambda row: row[\"truth\"],\n", + " y=lambda row: row[\"guess\"],\n", + " select=lambda row: weave.legacy.weave.panels.Group(\n", + " layoutMode='layer',\n", + " items={\n", + " 'count': row.count(),\n", + " 'color': weave.legacy.weave.panels.Color(row.count() / 50)\n", + " }\n", + " )\n", + " ),\n", + " 'selected': lambda confusion: confusion.selected()\n", + " }\n", + " )\n", + " return conf_mat" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ccf9a5a9-07b0-4967-a5ee-9255f9660ee3", + "metadata": {}, + "outputs": [], + "source": [ + "confusion_matrix(predictions)" + ] + }, + { + "cell_type": "markdown", + "id": "050f6119-7933-4084-9a9c-edb829c6c075", + "metadata": {}, + "source": [ + "# Step 3: Filter for incorrect predictions\n", + "\n", + "We can add a filter predicate to the Weave expression to select only the rows where the model's prediction doesn't match the correct label. Note that the highest-count / most-blue squares are absent in this version.\n", + "\n", + "Try opening this panel as a Weave Board by hovering over the right side and clicking \"Open in new tab\". You can explore more cells, add more panels, and share your work. Let us know if you find any interesting mistakes or patterns—for example, the mammal confused for an arachnid :)\n", + "\n", + "" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "75d50c33-9712-4ffe-9971-5552d32648bc", + "metadata": {}, + "outputs": [], + "source": [ + "mistakes_only = predictions.filter(lambda row: row[\"truth\"] != row[\"guess\"])\n", + "confusion_matrix(mistakes_only)" + ] + }, + { + "cell_type": "markdown", + "id": "9e4195fe-09cb-48e8-99a0-e01acf75e6c2", + "metadata": {}, + "source": [ + "# Bonus: Make a confusion matrix from the UI only\n", + "\n", + "To create a confusion matrix from the UI only:\n", + "1. Start with a Weave Table object saved with `weave.save()` and loaded into a panel with an expression of the form `get(\"wandb-artifact:///your_entity/your_project/obj:0123456789abcde/obj\")`.\n", + "2. Click the pencil \"Edit\" icon on the panel to open the panel config menu.\n", + "3. Change the Panel type to \"Facet\"—this is the main confusion matrix layout.\n", + "4. Set the X and Y axes for the intended confusion matrix, e.g to `row[\"truth\"]` and `row[\"guess\"]` for this example.\n", + "5. In the lower \"Properties\" section, change the child panel type from \"Expression\" to \"Group\", then change the layout from \"vertical\" to \"layer\" and select \"Add child\"—each child is a cell of the confusion matrix which \"groups\" or \"layers\" the count of items in that cell and the corresponding background color of the cell.\n", + "6. The \"Input\" of one child can be the count: enter `row.count` as the Weave expression. When you hit \"Run\", \"Panel type\" should update to \"Number\" and you should see numbers render in the confusion matrix panel.\n", + "7. Click \"Add Child\" and optionally add the cell background. Change \"Panel type\" to \"Color\" and enter `row.count / N` as the \"Input\", where N is some normalizing constant to scale your values from 0 for white to ~1.5 for the highest-saturation blue.\n", + "\n", + "Your settings menu should look something like this:\n", + "\n", + "" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/weave/legacy/examples/reference/control/Object Picker.ipynb b/weave/legacy/examples/reference/control/Object Picker.ipynb new file mode 100644 index 00000000000..abe0dca0244 --- /dev/null +++ b/weave/legacy/examples/reference/control/Object Picker.ipynb @@ -0,0 +1,121 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "f9712d25", + "metadata": {}, + "outputs": [], + "source": [ + "import weave\n", + "import weave.legacy.weave.panels" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2a60bff3", + "metadata": {}, + "outputs": [], + "source": [ + "ints = weave.save([1, 2, 3])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2185cbbc", + "metadata": {}, + "outputs": [], + "source": [ + "panel = weave.legacy.weave.panels.Group(\n", + " showExpressions=False,\n", + " items={\n", + " \"a\": weave.legacy.weave.panels.ObjectPicker(ints, label=\"a\"),\n", + " \"b\": weave.legacy.weave.panels.ObjectPicker(ints, label=\"b\"),\n", + " \"panel\": lambda a, b: a.config.choice + b.config.choice\n", + " })\n", + "panel" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a0f2f998", + "metadata": {}, + "outputs": [], + "source": [ + "# A more complex example with sidebar\n", + "\n", + "# ints = weave.legacy.weave.ops.objects(weave.types.Int(), 'latest', 9)\n", + "# weave.use(ints[0])\n", + "ints = weave.save([1, 2, 3], name='ints:latest')\n", + "panel = weave.legacy.weave.panels.Group(\n", + " preferHorizontal=True,\n", + " equalSize=True,\n", + " items={\n", + " \"sidebar\": weave.legacy.weave.panels.Group(\n", + " items={\n", + " \"a\": weave.legacy.weave.panels.ObjectPicker(ints, label=\"A\"),\n", + " \"b\": weave.legacy.weave.panels.ObjectPicker(ints, label=\"B\")\n", + " }\n", + " ),\n", + " \"main\": lambda sidebar: weave.legacy.weave.panels.Group(\n", + " items={\n", + " \"choice_values\": weave.legacy.weave.panels.Group(\n", + " preferHorizontal=True,\n", + " style=\"height:150px\",\n", + " items={\n", + " 'choice_a': weave.legacy.weave.panels.LabeledItem(\n", + " label=\"A\",\n", + " item=sidebar.config.items['a'].config.choice.execute()\n", + " ),\n", + " 'choice_b': weave.legacy.weave.panels.LabeledItem(\n", + " label=\"B\",\n", + " item=sidebar.config.items['b'].config.choice.execute()\n", + " )\n", + " }\n", + " ),\n", + " \"aggregates\": weave.legacy.weave.panels.Group(\n", + " preferHorizontal=True,\n", + " style=\"height:150px\",\n", + " items={\n", + " 'sum': weave.legacy.weave.panels.LabeledItem(\n", + " label='sum',\n", + " item=sidebar.config.items['a'].config.choice + sidebar.config.items['b'].config.choice\n", + " ),\n", + " 'product': weave.legacy.weave.panels.LabeledItem(\n", + " label='product',\n", + " item=sidebar.config.items['a'].config.choice * sidebar.config.items['b'].config.choice\n", + " )\n", + " }\n", + " )\n", + " }\n", + " )\n", + " })\n", + "panel" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/weave/legacy/examples/reference/control/Slider.ipynb b/weave/legacy/examples/reference/control/Slider.ipynb new file mode 100644 index 00000000000..43664e12d6d --- /dev/null +++ b/weave/legacy/examples/reference/control/Slider.ipynb @@ -0,0 +1,75 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "83fee798", + "metadata": {}, + "outputs": [], + "source": [ + "import weave\n", + "from weave.legacy.weave import panels" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6c85e3b7", + "metadata": {}, + "outputs": [], + "source": [ + "# Uncontrolled: panel creates its own state slot\n", + "panel = panels.Group(\n", + " items={\n", + " \"my_slider\": panels.Slider(),\n", + " \"val\": lambda my_slider: my_slider.value()\n", + " })\n", + "panel" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0dd8dd51", + "metadata": {}, + "outputs": [], + "source": [ + "# Controlled: state is external to panel\n", + "\n", + "panel = panels.Group(\n", + " vars={'slider_val': 5},\n", + " items={\n", + " 'state_value': lambda slider_val: slider_val,\n", + " 'slider': lambda slider_val: panels.Group(\n", + " items={\n", + " \"my_slider\": panels.Slider(slider_val),\n", + " \"val\": lambda my_slider: my_slider.value(),\n", + " \"my_slider2\": panels.Slider(slider_val)\n", + " })\n", + " })\n", + "panel" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/examples/reference/create_ops.ipynb b/weave/legacy/examples/reference/create_ops.ipynb similarity index 100% rename from examples/reference/create_ops.ipynb rename to weave/legacy/examples/reference/create_ops.ipynb diff --git a/weave/legacy/examples/reference/create_plots_ui_guide.ipynb b/weave/legacy/examples/reference/create_plots_ui_guide.ipynb new file mode 100644 index 00000000000..faf00a0b6d3 --- /dev/null +++ b/weave/legacy/examples/reference/create_plots_ui_guide.ipynb @@ -0,0 +1,330 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "1305d15c-9677-4f65-8cd8-338d1cb7aa82", + "metadata": {}, + "source": [ + "# Weave Plot: Interactively visualize your data\n", + "\n", + "In this tutorial, we will visualize data with the **Weave Plot Panel**:\n", + "* load a Pandas DataFrame or other tabular data into Weave \n", + "* visualize your data as a 2D plot of (x, y) points with **Weave Plot**\n", + "* customize the plot to filter or annotate the data\n", + "\n", + "We use an example dataset of [notable machine learning models](https://docs.google.com/spreadsheets/d/1AAIebjNsnJj_uKALHbXNfn3_YsT6sHXtCU0q7OIPuc4/edit#gid=0) to get started—you can try your own CSV file, Pandas DataFrame, or any public Google Sheets url.\n", + "\n", + "# 0: Setup\n", + "Import dependencies & login to W&B to save your work and share any visualizations you create." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "43053b45-eed1-445c-8bbd-05f113302060", + "metadata": {}, + "outputs": [], + "source": [ + "!pip install -qqq weave\n", + "import pandas as pd" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0d5645e7", + "metadata": {}, + "outputs": [], + "source": [ + "import wandb\n", + "wandb.login()\n", + "import weave\n", + "from weave.legacy.weave.show import show" + ] + }, + { + "cell_type": "markdown", + "id": "cb79fff5-f4fd-45db-a48e-c8581e0e5ad1", + "metadata": {}, + "source": [ + "# 1: Load your data as an interactive table\n", + "\n", + "Load a sample dataset of notable ML publications (560 rows, 33 columns as of Aug 8 2023). Feel free to edit these variables to\n", + "* load in your own Pandas DataFrame\n", + "* load a different CSV file\n", + "* modify the Google Sheets URL and sheet id to work with a different public spreadsheet" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8cf0eddd", + "metadata": {}, + "outputs": [], + "source": [ + "GOOGLE_SHEETS_URL = \"https://docs.google.com/spreadsheets/d/1AAIebjNsnJj_uKALHbXNfn3_YsT6sHXtCU0q7OIPuc4\"\n", + "SHEET_ID = \"0\"\n", + "CSV_SOURCE = f\"{GOOGLE_SHEETS_URL}/export?format=csv&gid={SHEET_ID}\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c52f0700", + "metadata": {}, + "outputs": [], + "source": [ + "df = pd.read_csv(CSV_SOURCE)\n", + "df.head()" + ] + }, + { + "cell_type": "markdown", + "id": "6e48d6d1-e935-4244-b9ea-500fdb18e0bd", + "metadata": {}, + "source": [ + "View an interactive panel with your data in one line [1]. As with any Weave Table Panel, you can sort or group by any column to explore and build some intuition for the dataset." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c1e62e5a-6a56-4581-a8e6-4f5d3bcc4d0e", + "metadata": {}, + "outputs": [], + "source": [ + "show(df)" + ] + }, + { + "cell_type": "markdown", + "id": "ad96850c-93e7-4b1f-a479-1e4f3655aebc", + "metadata": { + "scrolled": true + }, + "source": [ + "# 2: Plot the data in Weave\n", + "\n", + "You can convert any Weave Table into a **Weave Plot**. Change the panel type in the expression at the top of the panel from `table` to `plot`:\n", + "\n", + "\n", + "\n", + "Weave infers a reasonable view of your data based on the column types:\n", + "* two numerical columns x and y become the scatter plot axes\n", + "* each row is rendered as an (x, y) point on the resulting 2D grid, with a tooltip showing details on hover\n", + "* if available, the first string-type column becomes the label / the color shown in the legend.\n", + "\n", + "Before we dive into the detailed customization of a Weave Plot, how can we build with and iterate on this starter plot?\n", + "\n", + "## Full-screen a plot: Open as a Weave Board\n", + "\n", + "If you want more visual space or screen real estate to explore—zoom into details, zoom out for more context, iterate on multiple panel views in parallel—open any Weave Panel in a new browser tab. Mouse over the right-hand side and select \"Open in a new tab\"\n", + "\n", + "\n", + "## Multiple views in parallel: Refer to source data as you customize the plot\n", + "\n", + "Duplicate a panel so you can keep one copy in the `table` state and convert the second into a `plot`: click on the horizontal three-dot icon in the top right corner and select \"Duplicate\".\n", + "\n", + "\n", + "\n", + "## Resize one panel to adjust layout & iterate incrementally\n", + "\n", + "Combine these UX moves to iterate quickly on a neat layout: duplicate panels, resize one panel from a corner to a smaller portion of the grid to accommodate more panels, and independently modify individual panels until you're happy with the latest version.\n", + "\n", + "\n", + "Try the whole process starting from the single Table panel below:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b85268d4-ef89-4bf5-9aed-2b8650fcd701", + "metadata": {}, + "outputs": [], + "source": [ + "show(df)" + ] + }, + { + "cell_type": "markdown", + "id": "16cc1620-4d10-41a1-9220-55ca238d5015", + "metadata": {}, + "source": [ + "# 3. Explore a plot with zoom and interactive selection\n", + "\n", + "Interactively explore PanelPlot data with zoom and region highlighting (subset selection):\n", + "\n", + "## Zoom level: Click + drag to zoom into a seleted rectangle, double-click to reset\n", + "To zoom into a region of the plot:\n", + "* click on the magnifying glass icon in the bottom right corner\n", + "* click, hold, and drag to select a set of points—a gray rectangle shows the active selected region\n", + "* optionally repeat as you find new/different points of interest\n", + "* double-click anywhere on the plot area to reset to the original zoom level\n", + "\n", + "## Selected data: Use the .selected_data Weave op to show point details\n", + "To see the full row details for selected points:\n", + "* given one plot panel named `panelN`, create another `panelN+1` and enter `panelN.selected_data` as the new panel's expression. `PanelN+1` will now show any points highlighted in `panelN`\n", + "* to select points from `panelN`, first click on the pointer icon in the bottom right corner of `panelN`\n", + "* click, hold, and drag to select a set of points in `panelN`—a gray rectangle shows the active selected region\n", + "* view the full details for those points in panelN+1\n", + "\n", + "\n", + "\n", + "# 4. Customize Weave Plots: Choose columns or compose Weave expressions\n", + "\n", + "Weave plot panels for scatter, line, and bar charts share most of their configuration parameters. To customize a Weave Plot, click on the `Edit` pencil icon in the top right corner to open the settings menu.\n", + "\n", + "## X axis & Y axis\n", + "\n", + "Define the X and Y dimensions of a plot:\n", + "* choose a column from your source data:`row[\"your column name here\"]` (with helpful suggestions in the dropdown showing available column names as you type)\n", + "* further customize the Weave expression using arithmetic (`row[\"x\"] * 2`), combined columns (`row[\"a\"] + row[\"b\"]`), or more advanced Weave ops (`row[\"cost_per_month\"].avg`)\n", + "\n", + "Try modifying the X and Y of the starter plot: for example, is there a correlation between publication impact (citations) and compute costs? Try setting X = `row[\"Training compute cost (2020 USD)\"]` and Y = `row[\"Citations\"]`\n", + " \n", + "## Colors\n", + "\n", + "Point color is set by one of two input methods:\n", + "* default `Enter a Weave Expression` method: takes the [tableau10 color palette](https://vega.github.io/vega/docs/schemes/#tableau10) and cycles its 10 colors over the distinct values of the result of the Weave Expression, e.g the values of a string column. In our sample plot, the `Color` field defaults to `row[\"System\"]`, and the legend in the top right of the plot shows the [tableau10 colors]((https://vega.github.io/vega/docs/schemes/#tableau10)) repeating over the full list of unique \"System\" names in the source data. Try editing this to `row[\"Domain\"]` to see publication trends by field (the Games and Language models seem to have the highest compute costs).\n", + "* `Select via Dropdown` -> `Encode from series` : this option defaults to one blue color for a single series, and otherwise cycles through the same tableau10 color palette for multiple series\n", + "\n", + "## Tooltip\n", + "\n", + "The tooltip field determines the content displayed when the mouse hovers over a data point. This defaults to a subset of columns from the underlying Weave Table and can be configured via Weave Expression to select one or more columns and optionally link them with string literals for readability/formatting. The following expression might be a useful summary for our sample data to show the authors and date for each model in addition to the system name: `row[\"System\"] + \" - \" + row[\"Authors\"] + \"; \" + row[\"Publication date\"]`\n", + "\n", + "## Labels\n", + "\n", + "This menu (at the very bottom of the plot settings) optionally sets the titles of the X axis, Y axis, and Color legend to the provided text. In our sample, we might condense the X axis title to \"Compute cost\", expand the Color legend title as \"ML task type\", etc.\n", + "\n", + "Here is a [sample side-by-side Weave Board](https://weave.wandb.ai/?exp=get%28%0A++++%22wandb-artifact%3A%2F%2F%2Fstacey%2Fpivot%2Fdefault_plot_with_mods%3Alatest%2Fobj%22%29) of the default starter plot and the final state with all the above modifications (note that the tooltip appears to the bottom right of the cursor/corresponds to the point on the upper left corner of the textbox)\n", + "\n", + "\n", + "## Multiple series\n", + "\n", + "Use the \"New Series +\" button to add one or more series of points. You can treat each of the Series 1, 2, 3... in a plot as an independent group/collection of settings with a separate instance of the specifications described above. When a plot contains multiple series, all of the series are overlayed/rendered on the same plot area when visualized and their settings combined in the legend/titles/etc. With our sample dataset, we may want to configure a different series/visual settings for each domain: language models in one series, computer vision in another, etc.\n", + "\n", + "## Advanced configuration\n", + "\n", + "### Switch linear scale to log scale\n", + "\n", + "There are two ways to convert a plot axis to log scale:\n", + "\n", + "1. Use the **Advanced properties editor**: click the pencil \"edit\" icon on a plot to expand the config menu on the right, then select \"Advanced properties\" at the end of the first 'Properties\" section. Set x- and y- axes to be log scale instead of linear in the dropdown menu.\n", + "\n", + "2. Convert the Weave expression to **log base 10 mathematically**: e.g. convert `row[\"Parameters\"]` to `row[\"Parameters\"] ** 0.1`. This will not adust the grid scale markers like the first option, but it will apply to any expression/dimension (not only x- and y- axes) and can be easier to try in the flow of editing charts.\n", + "\n", + "\n", + "### Filter source data to remove outliers\n", + "\n", + "Sometimes a log scale or zooming in is still insufficient. You can remove outliers by filtering the input data based on a range of column values. Use the `.filter` Weave op on the `Input` field to plot only the points that meet the specified condition. For example, compare the default starter plot with dropping any models that have >= 1e12 parameters:\n", + "\n", + "\n", + "Try editing some of these settings from the Table below:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4b56735b-f35f-4bb7-bcb6-ce315a16ba30", + "metadata": {}, + "outputs": [], + "source": [ + "show(df)" + ] + }, + { + "cell_type": "markdown", + "id": "dd71e9f7-9c37-440b-8acf-7997baa60065", + "metadata": {}, + "source": [ + "# 5. Scatter plot: Customize point shape and size via explicit assignment or enumeration over option list\n", + "\n", + "The `Mark` setting intially determines the plot style: scatter plot, line plot, bar chart, etc. This defaults to \"auto\" and picks the best option based on the incoming data types. If `Mark` is set explicitly to `point`, this reveals controls for the shape and size of the points.\n", + "\n", + "## Point shape\n", + "\n", + "* defaults to \"Enter a Weave Expression\": as with the other config fields, write a Weave Expression that returns a list, where each distinct option in that list will cycle throught the built-in list of shape options\n", + "* \"Select via dropdown\" -> \"circle\", \"square\", \"cross\", \"diamond\", etc: this input method sets a specific literal point shape from the list of available options\n", + "* \"Select via dropdown\" -> \"Encode from series\": cycle the shape options over the multiple series in the plot\n", + "\n", + "In our example scenario, we could look at how compute sponsorship compares for notable papers: set `Shape` to `row[\"Compute Sponsor Categorization\"]` and observe circles for academia and squares for industry.\n", + "\n", + "## Point size (with approximately-bucketed enumeration)\n", + "\n", + "* default 100 / user-specified number: point size for scatter plots defaults to 100 and can be set to any other numerical value [2]\n", + "* Weave expression: as in other settings, the list of distinct values resulting from the user-specified Weave expression will cycle through five perceptually-distinguishable point sizes from smallest to larges\n", + "\n", + "In this sample plot, try log-scaling point size with compute costs: set `Size` to `row[\"Training compute cost (2020 USD)\"] ** 0.1`. \n", + "\n", + "### Combining all of these suggestions\n", + "\n", + "Here's a static view of the original plot with the modifications described—one more chance to try the fully-interactive and customizable version below\n", + "" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "06b8483f-1db0-4c2d-a27d-e0592a6ce160", + "metadata": {}, + "outputs": [], + "source": [ + "show(df)" + ] + }, + { + "cell_type": "markdown", + "id": "cfc8055c-0406-4b4c-a545-11f8c870d2d4", + "metadata": {}, + "source": [ + "# 6. Inspiration for many possible data exploration workflows\n", + "\n", + "Weave is a maximally general toolkit, and the path of any interactive visual exploration will depend on the data, context, interests, goals, etc. We've described the main options and useful features of Weave Plot to both illustrate concrete steps and hopefully spark your own questions and insights. In follow-up tutorials, we will cover settings specific to line plots and bar charts. We'd love to hear if you discover something interesting in this or your own iterations!\n", + "\n", + "# 7. End notes\n", + "\n", + "## [1] Viewing data in Weave\n", + "\n", + "When starting with a Pandas DataFrame, you have two options for getting data into Weave:\n", + "\n", + "### weave.show(my_dataframe)\n", + "\n", + "`weave.show(my_dataframe)` returns an interactive Weave Panel with the Pandas DataFrame as a Table. This is the fastest and simplest way to load an interactive panel with your data.\n", + "\n", + "### weave.save(weave.from_pandas(my_dataframe), name=\"my_dataframe\")\n", + "\n", + "If you'd like to save the DataFrame as a local object, first wrap it a in format Weave can parse using the `weave.from_pandas` op:\n", + "\n", + "```python\n", + "my_data = weave.save(weave.from_pandas(dataframe), name=\"my_dataframe\")\n", + "my_data\n", + "```\n", + "\n", + "## [2] Configuring point size\n", + "\n", + "Point size is currently \"perceptually clamped\" to around five distinguishable sizes: tiny, small, medium, larger, largest. Increasing/decreasing the literal number will not perceptually increase the biggest points or add more perceptible gradations of size. It may make the smallest points effectively invisible." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/weave/legacy/examples/reference/layout/Each.ipynb b/weave/legacy/examples/reference/layout/Each.ipynb new file mode 100644 index 00000000000..87429d18843 --- /dev/null +++ b/weave/legacy/examples/reference/layout/Each.ipynb @@ -0,0 +1,70 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "4093cfd3", + "metadata": {}, + "outputs": [], + "source": [ + "from PIL import Image\n", + "import weave\n", + "from weave.legacy.scripts import syndata\n", + "from weave.legacy.weave import panels" + ] + }, + { + "cell_type": "markdown", + "id": "b05e49a1", + "metadata": {}, + "source": [ + "## PanelEach\n", + "\n", + "PanelEach renders each item in a list.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b2cc5af5", + "metadata": {}, + "outputs": [], + "source": [ + "data = weave.save(syndata.random_metrics(100, 100))\n", + "panels.Each(data)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "36755a04", + "metadata": {}, + "outputs": [], + "source": [ + "ims = weave.save([Image.linear_gradient(\"L\").rotate(i * 4) for i in range(100)])\n", + "panels.Each(ims)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/weave/legacy/examples/reference/layout/Group & LabeledItem.ipynb b/weave/legacy/examples/reference/layout/Group & LabeledItem.ipynb new file mode 100644 index 00000000000..890e9b76bd3 --- /dev/null +++ b/weave/legacy/examples/reference/layout/Group & LabeledItem.ipynb @@ -0,0 +1,48 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "2e4a39ce", + "metadata": {}, + "outputs": [], + "source": [ + "import weave\n", + "import weave.legacy.weave.panels" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "32953f12", + "metadata": {}, + "outputs": [], + "source": [ + "# Group example\n", + "weave.legacy.weave.panels.Group(\n", + " items={\n", + " '0': weave.legacy.weave.panels.Group(\n", + " preferHorizontal=True,\n", + " items={\n", + " '0': weave.legacy.weave.panels.LabeledItem(item='item1', label='label1'),\n", + " '1': weave.legacy.weave.panels.LabeledItem(item='item2', label='label2'),\n", + " '2': weave.legacy.weave.panels.LabeledItem(item='item3', label='label3'),\n", + " }\n", + " ),\n", + " '1': weave.legacy.weave.panels.LabeledItem(item='item4', label='label4'),\n", + " '2': weave.legacy.weave.panels.LabeledItem(item='item5', label='label5')\n", + " }\n", + ")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/weave/legacy/examples/reference/markdown.ipynb b/weave/legacy/examples/reference/markdown.ipynb new file mode 100644 index 00000000000..a047c4186fb --- /dev/null +++ b/weave/legacy/examples/reference/markdown.ipynb @@ -0,0 +1,50 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "9357be91", + "metadata": {}, + "outputs": [], + "source": [ + "import weave" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "42e421dd", + "metadata": {}, + "outputs": [], + "source": [ + "weave.legacy.weave.panels.Board(\n", + " vars={},\n", + " panels=[weave.legacy.weave.panels.BoardPanel(\n", + " weave.legacy.weave.panels.PanelString(\"## I am markdown\\n\\nHear me roar\", mode=\"markdown\"),\n", + " id='some_markdown')\n", + " ])" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/weave/legacy/examples/reference/panels/plot/synced_axes.ipynb b/weave/legacy/examples/reference/panels/plot/synced_axes.ipynb new file mode 100644 index 00000000000..ed3d030632d --- /dev/null +++ b/weave/legacy/examples/reference/panels/plot/synced_axes.ipynb @@ -0,0 +1,153 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "f1022622", + "metadata": {}, + "outputs": [], + "source": [ + "import weave\n", + "import weave.legacy.weave.panels" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "af978245", + "metadata": {}, + "outputs": [], + "source": [ + "from weave.legacy.scripts import syndata" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4a2a4713", + "metadata": {}, + "outputs": [], + "source": [ + "data = weave.save(syndata.random_metrics(100, 100))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "85e9d219", + "metadata": {}, + "outputs": [], + "source": [ + "import weave\n", + "from weave.legacy.weave import ops_arrow\n", + "from weave.legacy.weave.ecosystem import wandb\n", + "import datetime\n", + "import random\n", + "random.seed(1)\n", + "\n", + "min_x = datetime.datetime(2023, 3, 22, 11, 0, 0)\n", + "max_x = min_x + datetime.timedelta(hours=1)\n", + "\n", + "users = ['nick', 'shawn', 'stacey', 'tim', 'danny']\n", + "model_versions = ['v1', 'v2']\n", + "\n", + "num_requests = 100\n", + "\n", + "def random_datetime_in_range() -> datetime.datetime:\n", + " min_timestamp = min_x.timestamp()\n", + " max_timestamp = max_x.timestamp()\n", + " random_timestamp = random.uniform(min_timestamp, max_timestamp)\n", + " return datetime.datetime.fromtimestamp(random_timestamp)\n", + "\n", + "def random_suggestion(n: int) -> str:\n", + " result = ''\n", + " for i in range(n):\n", + " result += random.choice('abcdefghijklmnopqrstuvwxyz')\n", + " return result\n", + "\n", + "\n", + "items = weave.save(\n", + " ops_arrow.to_arrow(sorted([\n", + " {\n", + " \"pred\": random_suggestion(7),\n", + " \"time\": random_datetime_in_range(),\n", + " \"user\": random.choice(users),\n", + " \"version\": random.choice(model_versions),\n", + " \"accepted\": random.choice([1, 1, 1, 0, 0]),\n", + " \"metric\": random.uniform(0, 1)\n", + " } \n", + " for _ in range(num_requests)\n", + " ], key=lambda d: d['time']))\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3d5919ee", + "metadata": {}, + "outputs": [], + "source": [ + "from weave.legacy.weave.panels.panel_plot import Signals, AxisSelections, Plot\n", + "from weave.legacy.weave import weave_internal\n", + "\n", + "\n", + "domain_node = weave_internal.const(None)\n", + "#domain_node = weave_internal.save(None)\n", + "\n", + "board = weave.legacy.weave.panels.Board(\n", + " vars={\n", + " 'predictions': items,\n", + " 'domain': domain_node,\n", + " },\n", + " panels=[\n", + " weave.legacy.weave.panels.BoardPanel(\n", + " lambda predictions, domain: Plot(\n", + " predictions,\n", + " x=lambda row: row[\"time\"],\n", + " y=lambda row: row[\"metric\"],\n", + " label=lambda row: row['user'],\n", + " mark=\"point\",\n", + " domain_x=domain,\n", + " ),\n", + " layout=weave.legacy.weave.panels.BoardPanelLayout(x=0, y=0, w=12, h=12)\n", + " ),\n", + " weave.legacy.weave.panels.BoardPanel(\n", + " lambda predictions, domain: Plot(\n", + " predictions,\n", + " x=lambda row: row[\"time\"],\n", + " y=lambda row: row[\"metric\"] * 2,\n", + " label=lambda row: row['version'],\n", + " mark=\"point\",\n", + " domain_x=domain,\n", + " ),\n", + " layout=weave.legacy.weave.panels.BoardPanelLayout(x=0, y=0, w=12, h=12)\n", + " ), \n", + " ]\n", + ")\n", + "board" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/weave/legacy/examples/reference/vis/Distribution.ipynb b/weave/legacy/examples/reference/vis/Distribution.ipynb new file mode 100644 index 00000000000..ea96aa5fc5a --- /dev/null +++ b/weave/legacy/examples/reference/vis/Distribution.ipynb @@ -0,0 +1,138 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "726127b0", + "metadata": {}, + "outputs": [], + "source": [ + "import random\n", + "import weave\n", + "# Weave package now defaults to eager mode, but lazy mode required for this example notebook for now.\\n\n", + "weave.use_lazy_execution()\n", + "from weave.legacy.weave.ecosystem import wandb" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a9875d92", + "metadata": {}, + "outputs": [], + "source": [ + "items = weave.save([\n", + " {'name': 'x',\n", + " 'loss1': [random.gauss(5, 2) for i in range(500)],\n", + " 'loss2': [random.gauss(5, 2) for i in range(500)],\n", + " 'str_val': [random.choice(['a', 'b', 'c']) for i in range(500)]},\n", + " {'name': 'y',\n", + " 'loss1': [random.gauss(9, 4) for i in range(500)],\n", + " 'loss2': [random.gauss(-1, 2) for i in range(500)],\n", + " 'str_val': [random.choice(['a', 'b', 'c']) for i in range(500)]},\n", + " ])\n", + "items" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a92bcc0e", + "metadata": {}, + "outputs": [], + "source": [ + "from weave.legacy.weave import weave_internal\n", + "# Numeric distribution\n", + "dist = wandb.Distribution(\n", + " items,\n", + " value_fn=lambda x: x['loss1'],\n", + " bin_size=0.1\n", + ")\n", + "dist" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fb380da9", + "metadata": {}, + "outputs": [], + "source": [ + "# String distribution\n", + "wandb.Distribution(\n", + " items,\n", + " value_fn=lambda x: x['str_val'],\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6db75ca1", + "metadata": {}, + "outputs": [], + "source": [ + "# Colored numeric distribution\n", + "wandb.Distribution(\n", + " items,\n", + " value_fn=lambda x: x['loss1'],\n", + " label_fn=lambda x: x['name'],\n", + " bin_size=0.2\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4eee28b9", + "metadata": {}, + "outputs": [], + "source": [ + "# Colored string distribution\n", + "wandb.Distribution(\n", + " items,\n", + " value_fn=lambda x: x['str_val'],\n", + " label_fn=lambda x: x['name'],\n", + " bin_size=0.2\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "30ab60fb", + "metadata": {}, + "outputs": [], + "source": [ + "# Labeled numeric distribution by nested value\n", + "wandb.Distribution(\n", + " items,\n", + " value_fn=lambda x: x['loss1'],\n", + " label_fn=lambda x: x['str_val'],\n", + " bin_size=0.2\n", + ")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/weave/legacy/examples/reference/vis/Scatter.ipynb b/weave/legacy/examples/reference/vis/Scatter.ipynb new file mode 100644 index 00000000000..6d11b709d1b --- /dev/null +++ b/weave/legacy/examples/reference/vis/Scatter.ipynb @@ -0,0 +1,101 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "81e1af41", + "metadata": {}, + "outputs": [], + "source": [ + "import random\n", + "import weave\n", + "import weave.legacy.weave.panels\n", + "from weave.legacy.weave.show import show\n", + "# Weave package now defaults to eager mode, but lazy mode required for this example notebook for now.\n", + "weave.use_lazy_execution()\n", + "from weave.legacy.weave.ecosystem import wandb" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b6687922", + "metadata": {}, + "outputs": [], + "source": [ + "data = weave.save([{\n", + " 'a': random.gauss(5, 2),\n", + " 'b': random.gauss(0, 9),\n", + " 'c': random.gauss(15, 0.9),\n", + " 'd': random.random(),\n", + " 'e': random.choice(['a', 'b'])} for i in range(500)])\n", + "data" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "726b9294", + "metadata": {}, + "outputs": [], + "source": [ + "panel = weave.legacy.weave.panels.Group(\n", + " items={\n", + " 'scatter': wandb.Scatter(data,\n", + " x_fn=lambda item: item['a'],\n", + " y_fn=lambda item: item['b'],\n", + " label_fn=lambda item: item['e']\n", + " ),\n", + " 'detail': lambda scatter: weave.legacy.weave.panels.Group(\n", + " style=\"height: 400px;\",\n", + " preferHorizontal=True,\n", + " items={\n", + " 'a_dist': weave.legacy.weave.panels.LabeledItem(\n", + " label='Distribution for metric a in selection',\n", + " item=wandb.Distribution(scatter.selected(),\n", + " value_fn=lambda item: item['a'],\n", + " label_fn=lambda item: item['e'],\n", + " bin_size=0.1)),\n", + " 'b_dist': weave.legacy.weave.panels.LabeledItem(\n", + " label='Distribution for metric b in selection',\n", + " item=wandb.Distribution(scatter.selected(),\n", + " value_fn=lambda item: item['b'],\n", + " label_fn=lambda item: item['e'],\n", + " bin_size=0.1))\n", + " }),\n", + " 'table': lambda scatter: weave.legacy.weave.panels.LabeledItem(\n", + " label='Selected items',\n", + " item=weave.legacy.weave.panels.Group(\n", + " style=\"height: 400px;\",\n", + " preferHorizontal=True,\n", + " items={\n", + " 'table': scatter.selected()\n", + " }))\n", + " }\n", + ")\n", + "show(panel, height=1000)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/weave/legacy/examples/reference/vis/derived_plots_from_tables.ipynb b/weave/legacy/examples/reference/vis/derived_plots_from_tables.ipynb new file mode 100644 index 00000000000..638de728518 --- /dev/null +++ b/weave/legacy/examples/reference/vis/derived_plots_from_tables.ipynb @@ -0,0 +1,111 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "14b320aa", + "metadata": {}, + "outputs": [], + "source": [ + "import weave\n", + "from weave.legacy.weave.ecosystem import wandb\n", + "\n", + "# Group tags are broken with caching\n", + "# import os \n", + "# os.environ[\"WEAVE_NO_CACHE\"] = 'true'" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bfc9678e", + "metadata": {}, + "outputs": [], + "source": [ + "table = weave.legacy.weave.panels.Table(list(range(100)))\n", + "\n", + "# Add Filter\n", + "table.config.tableState.set_filter_fn(lambda i: i < 50)\n", + "\n", + "# Add Grouping\n", + "table.config.tableState.add_column(lambda i: i % 3, 'col_name_a')\n", + "group_col_id = table.config.tableState.order[0]\n", + "# table.config.tableState.enable_groupby(group_col_id)\n", + "\n", + "# Add Selections (Note: Important to add after grouping so `i` is correctly typed)\n", + "table.config.tableState.add_column(lambda i: i ** 2, 'col_name_b')\n", + "# table.config.tableState.add_column(lambda i: i.max(), 'col_name_c')\n", + "\n", + "# Add Sort\n", + "table.config.tableState.enable_sort(group_col_id)\n", + "\n", + "\n", + "panel = weave.legacy.weave.panels.Group(\n", + " layoutMode='horizontal',\n", + " items={\n", + " \"table\": table,\n", + "# \"table_2\": lambda table: table.rows()[0]['col_name_a']\n", + " \"plot\": lambda table: weave.legacy.weave.panels.Plot(\n", + " table.all_rows(),\n", + " x=lambda row: row['col_name_a'],\n", + " y=lambda row: row['col_name_b'],\n", + " )\n", + " }\n", + ")\n", + "panel" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bf475571", + "metadata": {}, + "outputs": [], + "source": [ + "panel = weave.legacy.weave.panels.Group(\n", + " layoutMode='horizontal',\n", + " items={\n", + " \"table\": weave.legacy.weave.panels.Table(list(range(100)), columns=[\n", + " lambda i: i,\n", + " lambda i: i**2,\n", + " lambda i: i * 0 + 1\n", + " ]),\n", + " \"table_2\": lambda table: weave.legacy.weave.panels.Table(table.all_rows(), columns=[\n", + " lambda i: table.all_rows()['c_0'].max() - i['c_0'],\n", + " lambda i: i['c_1'] / 2,\n", + " lambda i: i['c_2'] - 2\n", + " ]),\n", + " \"plot\": lambda table, table_2: weave.legacy.weave.panels.Plot(\n", + " weave.legacy.weave.ops.make_list(a=table.all_rows(), b=table_2.all_rows()).concat(),\n", + " x=lambda row: row['c_0'],\n", + " y=lambda row: row['c_1'],\n", + " color=lambda row: row['c_2']\n", + " )\n", + " }\n", + ")\n", + "panel" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/internal_tools/Module deps viz.ipynb b/weave/legacy/internal_tools/Module deps viz.ipynb similarity index 100% rename from internal_tools/Module deps viz.ipynb rename to weave/legacy/internal_tools/Module deps viz.ipynb diff --git a/internal_tools/generate_notebook_tests.py b/weave/legacy/internal_tools/generate_notebook_tests.py similarity index 100% rename from internal_tools/generate_notebook_tests.py rename to weave/legacy/internal_tools/generate_notebook_tests.py diff --git a/weave/legacy/notebooks/Eval Board Syn.ipynb b/weave/legacy/notebooks/Eval Board Syn.ipynb index 50aa1e926c0..6a7ea54e9e6 100644 --- a/weave/legacy/notebooks/Eval Board Syn.ipynb +++ b/weave/legacy/notebooks/Eval Board Syn.ipynb @@ -1,318 +1,318 @@ { - "cells": [ - { - "cell_type": "markdown", - "id": "5214b543", - "metadata": {}, - "source": [ - "- Load two eval_results\n", - "\n", - "EvalResult\n", - "- example, label, result, item_summary" - ] + "cells": [ + { + "cell_type": "markdown", + "id": "5214b543", + "metadata": {}, + "source": [ + "- Load two eval_results\n", + "\n", + "EvalResult\n", + "- example, label, result, item_summary" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "314bb6b4", + "metadata": {}, + "outputs": [], + "source": [ + "import random\n", + "import string\n", + "import time\n", + "\n", + "import weave\n", + "\n", + "weave.use_frontend_devmode()\n", + "from weave.legacy.weave.panels import panel_board" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "550daef6", + "metadata": {}, + "outputs": [], + "source": [ + "def rand_string_n(n: int) -> str:\n", + " return \"\".join(\n", + " random.choice(string.ascii_uppercase + string.digits) for _ in range(n)\n", + " )\n", + "\n", + "\n", + "dataset_raw = [\n", + " {\n", + " \"id\": str(i),\n", + " \"example\": rand_string_n(10),\n", + " \"label\": random.choice(string.ascii_uppercase),\n", + " }\n", + " for i in range(50)\n", + "]\n", + "dataset = weave.save(dataset_raw, \"dataset\")\n", + "# dataset" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d0d930d8", + "metadata": {}, + "outputs": [], + "source": [ + "def predict(dataset_row, config):\n", + " if random.random() < config[\"correct_chance\"]:\n", + " return dataset_row[\"label\"]\n", + " return random.choice(string.ascii_uppercase)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "eb86b95c", + "metadata": {}, + "outputs": [], + "source": [ + "def evaluate(dataset, predict_config):\n", + " eval_result = []\n", + " correct_count = 0\n", + " count = 0\n", + " for dataset_row in dataset:\n", + " start_time = time.time()\n", + " result = predict(dataset_row, predict_config)\n", + " latency = time.time() - start_time\n", + " latency = random.gauss(\n", + " predict_config[\"latency_mu\"], predict_config[\"latency_sigma\"]\n", + " )\n", + " correct = dataset_row[\"label\"] == result\n", + " if correct:\n", + " correct_count += 1\n", + " count += 1\n", + " eval_result.append(\n", + " {\n", + " \"dataset_id\": dataset_row[\"id\"],\n", + " \"result\": result,\n", + " \"summary\": {\"latency\": latency, \"correct\": correct},\n", + " }\n", + " )\n", + " return {\n", + " \"config\": predict_config,\n", + " \"eval_table\": eval_result,\n", + " \"summary\": {\"accuracy\": correct_count / len(dataset)},\n", + " }" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "05d16a5e", + "metadata": {}, + "outputs": [], + "source": [ + "eval_result_raw0 = evaluate(\n", + " dataset_raw, {\"correct_chance\": 0.5, \"latency_mu\": 0.3, \"latency_sigma\": 0.1}\n", + ")\n", + "eval_result_raw1 = evaluate(\n", + " dataset_raw, {\"correct_chance\": 0.5, \"latency_mu\": 0.4, \"latency_sigma\": 0.2}\n", + ")\n", + "eval_result0 = weave.save(eval_result_raw0, \"eval_result0\")\n", + "eval_result1 = weave.save(eval_result_raw1, \"eval_result1\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e8065ad6", + "metadata": {}, + "outputs": [], + "source": [ + "varbar = panel_board.varbar()\n", + "\n", + "dataset_var = varbar.add(\"dataset\", dataset)\n", + "eval_result0_var = varbar.add(\"eval_result0\", eval_result0)\n", + "eval_result1_var = varbar.add(\"eval_result1\", eval_result1)\n", + "\n", + "summary = varbar.add(\n", + " \"summary\",\n", + " weave.legacy.weave.ops.make_list(\n", + " a=weave.legacy.weave.ops.TypedDict.merge(\n", + " weave.legacy.weave.ops.dict_(name=\"res0\"), eval_result0_var[\"summary\"]\n", + " ),\n", + " b=weave.legacy.weave.ops.TypedDict.merge(\n", + " weave.legacy.weave.ops.dict_(name=\"res1\"), eval_result1_var[\"summary\"]\n", + " ),\n", + " ),\n", + ")\n", + "\n", + "weave.legacy.weave.ops.make_list(\n", + " a=eval_result0_var[\"eval_table\"], b=eval_result0_var[\"eval_table\"]\n", + ")\n", + "\n", + "concatted_evals = varbar.add(\n", + " \"concatted_evals\",\n", + " weave.legacy.weave.ops.List.concat(\n", + " weave.legacy.weave.ops.make_list(\n", + " a=eval_result0_var[\"eval_table\"].map(\n", + " lambda row: weave.legacy.weave.ops.TypedDict.merge(\n", + " weave.legacy.weave.ops.dict_(name=\"res0\"), row\n", + " )\n", + " ),\n", + " b=eval_result1_var[\"eval_table\"].map(\n", + " lambda row: weave.legacy.weave.ops.TypedDict.merge(\n", + " weave.legacy.weave.ops.dict_(name=\"res1\"), row\n", + " )\n", + " ),\n", + " )\n", + " ),\n", + ")\n", + "\n", + "# join evals together first\n", + "joined_evals = varbar.add(\n", + " \"joined_evals\",\n", + " weave.legacy.weave.ops.join_all(\n", + " weave.legacy.weave.ops.make_list(\n", + " a=eval_result0_var[\"eval_table\"], b=eval_result1_var[\"eval_table\"]\n", + " ),\n", + " lambda row: row[\"dataset_id\"],\n", + " False,\n", + " ),\n", + ")\n", + "\n", + "# then join dataset to evals\n", + "dataset_evals = varbar.add(\n", + " \"dataset_evals\",\n", + " weave.legacy.weave.ops.join_2(\n", + " dataset_var,\n", + " joined_evals,\n", + " lambda row: row[\"id\"],\n", + " lambda row: row[\"dataset_id\"][0],\n", + " \"dataset\",\n", + " \"evals\",\n", + " False,\n", + " False,\n", + " ),\n", + ")\n", + "\n", + "\n", + "main = weave.legacy.weave.panels.Group(\n", + " layoutMode=\"grid\",\n", + " showExpressions=True,\n", + " enableAddPanel=True,\n", + ")\n", + "\n", + "#### Run/config info TODO\n", + "\n", + "#### Summary info\n", + "\n", + "main.add(\n", + " \"accuracy\",\n", + " weave.legacy.weave.panels.Plot(\n", + " summary,\n", + " x=lambda row: row[\"accuracy\"],\n", + " y=lambda row: row[\"name\"],\n", + " color=lambda row: row[\"name\"],\n", + " ),\n", + " layout=weave.legacy.weave.panels.GroupPanelLayout(x=0, y=0, w=12, h=4),\n", + ")\n", + "\n", + "\n", + "main.add(\n", + " \"latency\",\n", + " weave.legacy.weave.panels.Plot(\n", + " concatted_evals,\n", + " x=lambda row: row[\"summary\"][\"latency\"],\n", + " y=lambda row: row[\"name\"],\n", + " color=lambda row: row[\"name\"],\n", + " mark=\"boxplot\",\n", + " ),\n", + " layout=weave.legacy.weave.panels.GroupPanelLayout(x=12, y=0, w=12, h=4),\n", + ")\n", + "\n", + "# ct = main.add('concat_t', concatted_evals, layout=weave.legacy.weave.panels.GroupPanelLayout(x=0, y=4, w=24, h=12))\n", + "# main.add('dataset_table', dataset)\n", + "# main.add('joined_evals', joined_evals)\n", + "# main.add('dataset_evals', dataset_evals, layout=weave.legacy.weave.panels.GroupPanelLayout(x=0, y=4, w=24, h=6))\n", + "\n", + "##### Example details\n", + "\n", + "# more ideas: show examples that all got wrong, or that are confusing\n", + "\n", + "faceted_view = weave.legacy.weave.panels.Facet(\n", + " dataset_evals,\n", + " x=lambda row: row[\"evals.summary\"][0][\"correct\"],\n", + " y=lambda row: row[\"evals.summary\"][1][\"correct\"],\n", + " select=lambda row: row.count(),\n", + ")\n", + "\n", + "faceted = main.add(\n", + " \"faceted\",\n", + " faceted_view,\n", + " layout=weave.legacy.weave.panels.GroupPanelLayout(x=0, y=4, w=12, h=6),\n", + ")\n", + "\n", + "main.add(\n", + " \"example_latencies\",\n", + " weave.legacy.weave.panels.Plot(\n", + " dataset_evals,\n", + " x=lambda row: row[\"evals.summary\"][\"latency\"][0],\n", + " y=lambda row: row[\"evals.summary\"][\"latency\"][1],\n", + " ),\n", + " layout=weave.legacy.weave.panels.GroupPanelLayout(x=12, y=4, w=12, h=6),\n", + ")\n", + "\n", + "faceted_sel = weave.legacy.weave.panels.Table(faceted.selected())\n", + "faceted_sel.config.rowSize = 2\n", + "faceted_sel.add_column(lambda row: row[\"dataset.id\"], \"id\")\n", + "faceted_sel.add_column(lambda row: row[\"dataset.example\"], \"example\")\n", + "faceted_sel.add_column(lambda row: row[\"dataset.label\"], \"label\")\n", + "faceted_sel.add_column(\n", + " lambda row: weave.legacy.weave.ops.dict_(\n", + " res0=row[\"evals.result\"][0], res1=row[\"evals.result\"][1]\n", + " ),\n", + " \"result\",\n", + ")\n", + "faceted_sel.add_column(\n", + " lambda row: weave.legacy.weave.ops.dict_(\n", + " res0=row[\"evals.summary\"][0][\"correct\"], res1=row[\"evals.summary\"][1][\"correct\"]\n", + " ),\n", + " \"correct\",\n", + ")\n", + "faceted_sel.add_column(\n", + " lambda row: weave.legacy.weave.ops.dict_(\n", + " res0=row[\"evals.summary\"][0][\"latency\"], res1=row[\"evals.summary\"][1][\"latency\"]\n", + " ),\n", + " \"latency\",\n", + ")\n", + "\n", + "main.add(\n", + " \"faceted_sel\",\n", + " faceted_sel,\n", + " layout=weave.legacy.weave.panels.GroupPanelLayout(x=0, y=10, w=24, h=12),\n", + ")\n", + "\n", + "weave.legacy.weave.panels.Board(vars=varbar, panels=main)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } }, - { - "cell_type": "code", - "execution_count": null, - "id": "314bb6b4", - "metadata": {}, - "outputs": [], - "source": [ - "import random\n", - "import string\n", - "import time\n", - "\n", - "import weave\n", - "\n", - "weave.use_frontend_devmode()\n", - "from weave.legacy.panels import panel_board" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "550daef6", - "metadata": {}, - "outputs": [], - "source": [ - "def rand_string_n(n: int) -> str:\n", - " return \"\".join(\n", - " random.choice(string.ascii_uppercase + string.digits) for _ in range(n)\n", - " )\n", - "\n", - "\n", - "dataset_raw = [\n", - " {\n", - " \"id\": str(i),\n", - " \"example\": rand_string_n(10),\n", - " \"label\": random.choice(string.ascii_uppercase),\n", - " }\n", - " for i in range(50)\n", - "]\n", - "dataset = weave.save(dataset_raw, \"dataset\")\n", - "# dataset" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d0d930d8", - "metadata": {}, - "outputs": [], - "source": [ - "def predict(dataset_row, config):\n", - " if random.random() < config[\"correct_chance\"]:\n", - " return dataset_row[\"label\"]\n", - " return random.choice(string.ascii_uppercase)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "eb86b95c", - "metadata": {}, - "outputs": [], - "source": [ - "def evaluate(dataset, predict_config):\n", - " eval_result = []\n", - " correct_count = 0\n", - " count = 0\n", - " for dataset_row in dataset:\n", - " start_time = time.time()\n", - " result = predict(dataset_row, predict_config)\n", - " latency = time.time() - start_time\n", - " latency = random.gauss(\n", - " predict_config[\"latency_mu\"], predict_config[\"latency_sigma\"]\n", - " )\n", - " correct = dataset_row[\"label\"] == result\n", - " if correct:\n", - " correct_count += 1\n", - " count += 1\n", - " eval_result.append(\n", - " {\n", - " \"dataset_id\": dataset_row[\"id\"],\n", - " \"result\": result,\n", - " \"summary\": {\"latency\": latency, \"correct\": correct},\n", - " }\n", - " )\n", - " return {\n", - " \"config\": predict_config,\n", - " \"eval_table\": eval_result,\n", - " \"summary\": {\"accuracy\": correct_count / len(dataset)},\n", - " }" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "05d16a5e", - "metadata": {}, - "outputs": [], - "source": [ - "eval_result_raw0 = evaluate(\n", - " dataset_raw, {\"correct_chance\": 0.5, \"latency_mu\": 0.3, \"latency_sigma\": 0.1}\n", - ")\n", - "eval_result_raw1 = evaluate(\n", - " dataset_raw, {\"correct_chance\": 0.5, \"latency_mu\": 0.4, \"latency_sigma\": 0.2}\n", - ")\n", - "eval_result0 = weave.save(eval_result_raw0, \"eval_result0\")\n", - "eval_result1 = weave.save(eval_result_raw1, \"eval_result1\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "e8065ad6", - "metadata": {}, - "outputs": [], - "source": [ - "varbar = panel_board.varbar()\n", - "\n", - "dataset_var = varbar.add(\"dataset\", dataset)\n", - "eval_result0_var = varbar.add(\"eval_result0\", eval_result0)\n", - "eval_result1_var = varbar.add(\"eval_result1\", eval_result1)\n", - "\n", - "summary = varbar.add(\n", - " \"summary\",\n", - " weave.legacy.ops.make_list(\n", - " a=weave.legacy.ops.TypedDict.merge(\n", - " weave.legacy.ops.dict_(name=\"res0\"), eval_result0_var[\"summary\"]\n", - " ),\n", - " b=weave.legacy.ops.TypedDict.merge(\n", - " weave.legacy.ops.dict_(name=\"res1\"), eval_result1_var[\"summary\"]\n", - " ),\n", - " ),\n", - ")\n", - "\n", - "weave.legacy.ops.make_list(\n", - " a=eval_result0_var[\"eval_table\"], b=eval_result0_var[\"eval_table\"]\n", - ")\n", - "\n", - "concatted_evals = varbar.add(\n", - " \"concatted_evals\",\n", - " weave.legacy.ops.List.concat(\n", - " weave.legacy.ops.make_list(\n", - " a=eval_result0_var[\"eval_table\"].map(\n", - " lambda row: weave.legacy.ops.TypedDict.merge(\n", - " weave.legacy.ops.dict_(name=\"res0\"), row\n", - " )\n", - " ),\n", - " b=eval_result1_var[\"eval_table\"].map(\n", - " lambda row: weave.legacy.ops.TypedDict.merge(\n", - " weave.legacy.ops.dict_(name=\"res1\"), row\n", - " )\n", - " ),\n", - " )\n", - " ),\n", - ")\n", - "\n", - "# join evals together first\n", - "joined_evals = varbar.add(\n", - " \"joined_evals\",\n", - " weave.legacy.ops.join_all(\n", - " weave.legacy.ops.make_list(\n", - " a=eval_result0_var[\"eval_table\"], b=eval_result1_var[\"eval_table\"]\n", - " ),\n", - " lambda row: row[\"dataset_id\"],\n", - " False,\n", - " ),\n", - ")\n", - "\n", - "# then join dataset to evals\n", - "dataset_evals = varbar.add(\n", - " \"dataset_evals\",\n", - " weave.legacy.ops.join_2(\n", - " dataset_var,\n", - " joined_evals,\n", - " lambda row: row[\"id\"],\n", - " lambda row: row[\"dataset_id\"][0],\n", - " \"dataset\",\n", - " \"evals\",\n", - " False,\n", - " False,\n", - " ),\n", - ")\n", - "\n", - "\n", - "main = weave.legacy.panels.Group(\n", - " layoutMode=\"grid\",\n", - " showExpressions=True,\n", - " enableAddPanel=True,\n", - ")\n", - "\n", - "#### Run/config info TODO\n", - "\n", - "#### Summary info\n", - "\n", - "main.add(\n", - " \"accuracy\",\n", - " weave.legacy.panels.Plot(\n", - " summary,\n", - " x=lambda row: row[\"accuracy\"],\n", - " y=lambda row: row[\"name\"],\n", - " color=lambda row: row[\"name\"],\n", - " ),\n", - " layout=weave.legacy.panels.GroupPanelLayout(x=0, y=0, w=12, h=4),\n", - ")\n", - "\n", - "\n", - "main.add(\n", - " \"latency\",\n", - " weave.legacy.panels.Plot(\n", - " concatted_evals,\n", - " x=lambda row: row[\"summary\"][\"latency\"],\n", - " y=lambda row: row[\"name\"],\n", - " color=lambda row: row[\"name\"],\n", - " mark=\"boxplot\",\n", - " ),\n", - " layout=weave.legacy.panels.GroupPanelLayout(x=12, y=0, w=12, h=4),\n", - ")\n", - "\n", - "# ct = main.add('concat_t', concatted_evals, layout=weave.legacy.panels.GroupPanelLayout(x=0, y=4, w=24, h=12))\n", - "# main.add('dataset_table', dataset)\n", - "# main.add('joined_evals', joined_evals)\n", - "# main.add('dataset_evals', dataset_evals, layout=weave.legacy.panels.GroupPanelLayout(x=0, y=4, w=24, h=6))\n", - "\n", - "##### Example details\n", - "\n", - "# more ideas: show examples that all got wrong, or that are confusing\n", - "\n", - "faceted_view = weave.legacy.panels.Facet(\n", - " dataset_evals,\n", - " x=lambda row: row[\"evals.summary\"][0][\"correct\"],\n", - " y=lambda row: row[\"evals.summary\"][1][\"correct\"],\n", - " select=lambda row: row.count(),\n", - ")\n", - "\n", - "faceted = main.add(\n", - " \"faceted\",\n", - " faceted_view,\n", - " layout=weave.legacy.panels.GroupPanelLayout(x=0, y=4, w=12, h=6),\n", - ")\n", - "\n", - "main.add(\n", - " \"example_latencies\",\n", - " weave.legacy.panels.Plot(\n", - " dataset_evals,\n", - " x=lambda row: row[\"evals.summary\"][\"latency\"][0],\n", - " y=lambda row: row[\"evals.summary\"][\"latency\"][1],\n", - " ),\n", - " layout=weave.legacy.panels.GroupPanelLayout(x=12, y=4, w=12, h=6),\n", - ")\n", - "\n", - "faceted_sel = weave.legacy.panels.Table(faceted.selected())\n", - "faceted_sel.config.rowSize = 2\n", - "faceted_sel.add_column(lambda row: row[\"dataset.id\"], \"id\")\n", - "faceted_sel.add_column(lambda row: row[\"dataset.example\"], \"example\")\n", - "faceted_sel.add_column(lambda row: row[\"dataset.label\"], \"label\")\n", - "faceted_sel.add_column(\n", - " lambda row: weave.legacy.ops.dict_(\n", - " res0=row[\"evals.result\"][0], res1=row[\"evals.result\"][1]\n", - " ),\n", - " \"result\",\n", - ")\n", - "faceted_sel.add_column(\n", - " lambda row: weave.legacy.ops.dict_(\n", - " res0=row[\"evals.summary\"][0][\"correct\"], res1=row[\"evals.summary\"][1][\"correct\"]\n", - " ),\n", - " \"correct\",\n", - ")\n", - "faceted_sel.add_column(\n", - " lambda row: weave.legacy.ops.dict_(\n", - " res0=row[\"evals.summary\"][0][\"latency\"], res1=row[\"evals.summary\"][1][\"latency\"]\n", - " ),\n", - " \"latency\",\n", - ")\n", - "\n", - "main.add(\n", - " \"faceted_sel\",\n", - " faceted_sel,\n", - " layout=weave.legacy.panels.GroupPanelLayout(x=0, y=10, w=24, h=12),\n", - ")\n", - "\n", - "weave.legacy.panels.Board(vars=varbar, panels=main)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 + "nbformat": 4, + "nbformat_minor": 5 } diff --git a/weave/legacy/notebooks/Eval Board.ipynb b/weave/legacy/notebooks/Eval Board.ipynb index 6c64694dc68..1cf4b810b8a 100644 --- a/weave/legacy/notebooks/Eval Board.ipynb +++ b/weave/legacy/notebooks/Eval Board.ipynb @@ -10,11 +10,11 @@ "import typing\n", "\n", "import weave\n", - "from weave import weave_internal\n", + "from weave.legacy.weave import weave_internal\n", "\n", "weave.use_frontend_devmode()\n", - "from weave.legacy import ops_domain\n", - "from weave.legacy.panels import panel_board" + "from weave.legacy.weave import ops_domain\n", + "from weave.legacy.weave.panels import panel_board" ] }, { @@ -64,7 +64,7 @@ "entity = ops_domain.entity(entity_name_val)\n", "entity_name = varbar.add(\n", " \"entity_name\",\n", - " weave.legacy.panels.Dropdown(\n", + " weave.legacy.weave.panels.Dropdown(\n", " entity_name_val, choices=ops_domain.viewer().entities().name()\n", " ),\n", ")\n", @@ -73,13 +73,15 @@ "project = ops_domain.project(entity_name_val, project_name_val)\n", "project_name = varbar.add(\n", " \"project_name\",\n", - " weave.legacy.panels.Dropdown(project_name_val, choices=entity.projects().name()),\n", + " weave.legacy.weave.panels.Dropdown(\n", + " project_name_val, choices=entity.projects().name()\n", + " ),\n", ")\n", "\n", "dataset_name_val = varbar.add(\"dataset_name_val\", \"my_dataset1\", hidden=True)\n", "dataset = varbar.add(\n", " \"dataset\",\n", - " weave.legacy.ops.get(\n", + " weave.legacy.weave.ops.get(\n", " weave_internal.const(\"wandb-artifact:///\")\n", " + entity_name_val\n", " + \"/\"\n", @@ -92,7 +94,7 @@ ")\n", "dataset_ref = varbar.add(\n", " \"dataset_ref\",\n", - " weave.legacy.ops.ref(\n", + " weave.legacy.weave.ops.ref(\n", " weave_internal.const(\"wandb-artifact:///\")\n", " + entity_name_val\n", " + \"/\"\n", @@ -104,7 +106,7 @@ ")\n", "dataset_name = varbar.add(\n", " \"dataset_name\",\n", - " weave.legacy.panels.Dropdown(\n", + " weave.legacy.weave.panels.Dropdown(\n", " dataset_name_val, choices=project.artifactType(\"Dataset\").artifacts().name()\n", " ),\n", ")\n", @@ -117,7 +119,7 @@ "\n", "# want a really nice data grid of the table metric comparsion per row\n", "\n", - "main = weave.legacy.panels.Group(\n", + "main = weave.legacy.weave.panels.Group(\n", " layoutMode=\"grid\",\n", " showExpressions=True,\n", " enableAddPanel=True,\n", @@ -125,7 +127,7 @@ "\n", "main.add(\"ename\", entity_name_val)\n", "main.add(\"pname\", project_name_val)\n", - "main.add(\"dsrows\", weave.legacy.ops.obj_getattr(dataset, \"rows\"))\n", + "main.add(\"dsrows\", weave.legacy.weave.ops.obj_getattr(dataset, \"rows\"))\n", "# main.add(\"artifact\", dataset_ref.artifact())\n", "\n", "# I Need a programmable Table. One that's columns are defined by Nodes\n", @@ -133,7 +135,7 @@ "# Can I do this with Weaveify?\n", "# Like I can pass a function in in the Board that returns the panel I want maybe?\n", "\n", - "weave.legacy.panels.Board(vars=varbar, panels=main)" + "weave.legacy.weave.panels.Board(vars=varbar, panels=main)" ] } ], diff --git a/weave/legacy/notebooks/Text Extraction Eval.ipynb b/weave/legacy/notebooks/Text Extraction Eval.ipynb index 64daefac32b..a2d2705eb97 100644 --- a/weave/legacy/notebooks/Text Extraction Eval.ipynb +++ b/weave/legacy/notebooks/Text Extraction Eval.ipynb @@ -1,445 +1,445 @@ { - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "04bba52c", - "metadata": {}, - "outputs": [], - "source": [ - "import glob\n", - "import os\n", - "import random\n", - "import time\n", - "import typing\n", - "\n", - "import weave\n", - "\n", - "weave.use_frontend_devmode()" - ] + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "04bba52c", + "metadata": {}, + "outputs": [], + "source": [ + "import glob\n", + "import os\n", + "import random\n", + "import time\n", + "import typing\n", + "\n", + "import weave\n", + "\n", + "weave.use_frontend_devmode()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8172d67c", + "metadata": {}, + "outputs": [], + "source": [ + "raw_labels = {\n", + " \"Articles_of_Incorporation_Real_Example_3_txt\": {\n", + " \"name\": \"HealthFirst Solutions LLC\",\n", + " \"shares\": 500000,\n", + " },\n", + " \"Articles_of_Incorporation_Real_Example_2_txt\": {\n", + " \"name\": \"GreenLeaf LLC\",\n", + " \"shares\": None,\n", + " \"directors\": [\"Sarah Miller\", \"Daniel Lee\"],\n", + " },\n", + " \"Articles_of_Incorporation_Real_Example_1_txt\": {\n", + " \"name\": \"TechBoost Corp\",\n", + " \"shares\": 1000000,\n", + " },\n", + " \"Highly_Varied_Article_of_Incorporation_10_txt\": {\n", + " \"name\": \"Brown, Fernandez and Smith\",\n", + " \"shares\": 41141,\n", + " },\n", + " \"Highly_Varied_Article_of_Incorporation_9_txt\": {\n", + " \"name\": \"Ruiz-Goodman\",\n", + " \"shares\": 31783,\n", + " },\n", + " \"Highly_Varied_Article_of_Incorporation_8_txt\": {\n", + " \"name\": \"Gibson, Hunt and Davidson\",\n", + " \"shares\": 96403,\n", + " },\n", + " \"Highly_Varied_Article_of_Incorporation_7_txt\": {\n", + " \"name\": \"Boyd-Browning\",\n", + " \"shares\": 41300,\n", + " },\n", + " \"Highly_Varied_Article_of_Incorporation_6_txt\": {\n", + " \"name\": \"Newton, Moreno and Yang\",\n", + " \"shares\": 73981,\n", + " },\n", + " \"Highly_Varied_Article_of_Incorporation_5_txt\": {\n", + " \"name\": \"Matthews and Sons\",\n", + " \"shares\": 98608,\n", + " },\n", + " \"Highly_Varied_Article_of_Incorporation_4_txt\": {\n", + " \"name\": \"Moore LLC\",\n", + " \"shares\": 5732,\n", + " },\n", + " \"Highly_Varied_Article_of_Incorporation_3_txt\": {\n", + " \"name\": \"Mullen Inc\",\n", + " \"shares\": 76197,\n", + " },\n", + " \"Highly_Varied_Article_of_Incorporation_2_txt\": {\n", + " \"name\": \"Ellis and Sons\",\n", + " \"shares\": 54183,\n", + " },\n", + " \"Highly_Varied_Article_of_Incorporation_1_txt\": {\n", + " \"name\": \"French, Wyatt and Coleman\",\n", + " \"shares\": 78821,\n", + " },\n", + "}\n", + "\n", + "\n", + "def read_dataset(root):\n", + " dataset_rows = []\n", + " for p in glob.glob(os.path.join(root, \"*.txt\")):\n", + " # Have to do replace here because of weave '.' access issues\n", + " example_id = os.path.basename(p).replace(\".\", \"_\")\n", + " label = raw_labels.get(example_id)\n", + " if label:\n", + " dataset_rows.append(\n", + " {\"id\": example_id, \"example\": open(p).read(), \"label\": label}\n", + " )\n", + " return dataset_rows" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cb93f2a4", + "metadata": {}, + "outputs": [], + "source": [ + "# Can't just make our own types, server won't deserialize.\n", + "# A fairly easy fix.\n", + "@weave.type()\n", + "class Dataset:\n", + " rows: list[typing.Any]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "92c6535c", + "metadata": {}, + "outputs": [], + "source": [ + "raw_dataset = read_dataset(\"/Users/shawn/datasets/aoi\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7e803efd", + "metadata": {}, + "outputs": [], + "source": [ + "# raw_dataset" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fc723706", + "metadata": { + "scrolled": false + }, + "outputs": [], + "source": [ + "dataset = weave.save(Dataset(raw_dataset), \"my_dataset5\")\n", + "# Now, here I really want to make my own labels in the UI immediately.\n", + "# where should the added column go? A new version of this dataset?\n", + "# yeah sure why not.\n", + "# What's missing for editing to be good?\n", + "# - batch editing, ie make a bunch of changes and choose where/how to save" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4a99df38", + "metadata": {}, + "outputs": [], + "source": [ + "# published = weave.publish(Dataset(raw_dataset), 'weave-flow1/my_dataset1')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "77c71a23", + "metadata": {}, + "outputs": [], + "source": [ + "dataset.rows\n", + "# Here i went to render labels next to dataset.\n", + "# I need access to labels in the notebook memory... would be easy enough to pass in" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "331d88d6", + "metadata": {}, + "outputs": [], + "source": [ + "import re\n", + "\n", + "\n", + "def split_paragraphs(doc):\n", + " lines = [l.strip() for l in doc.split(\"\\n\")]\n", + " stripped_doc = \"\\n\".join(lines)\n", + " return [p.strip() for p in stripped_doc.split(\"\\n\\n\")]\n", + "\n", + "\n", + "def find_first_numeric(s):\n", + " match = re.search(r\"\\d+\", s)\n", + " if match is None:\n", + " return None\n", + " return int(match.group().replace(\",\", \"\"))\n", + "\n", + "\n", + "def predict(dataset_row, config):\n", + " paragraphs = split_paragraphs(dataset_row[\"example\"])\n", + " capital_paragraph = None\n", + " name_paragraph = None\n", + " for p in paragraphs:\n", + " if \"name\" in p.lower():\n", + " name_paragraph = p\n", + " if \"share\" in p.lower():\n", + " capital_paragraph = p\n", + " result = {\n", + " \"name\": None,\n", + " \"shares\": None,\n", + " }\n", + " if capital_paragraph:\n", + " paragraph_start = config.get(\"shares_skip_chars\", 0)\n", + " result[\"shares\"] = find_first_numeric(capital_paragraph[paragraph_start:])\n", + " if name_paragraph:\n", + " match = re.search(r\"is \", name_paragraph)\n", + " if match is not None:\n", + " result[\"name\"] = name_paragraph[match.end() :]\n", + " if result[\"name\"] and config.get(\"name_up_to_period\"):\n", + " match = re.search(r\"\\.\", result[\"name\"])\n", + " if match is not None:\n", + " result[\"name\"] = result[\"name\"][: match.start()]\n", + " return result" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b2378174", + "metadata": {}, + "outputs": [], + "source": [ + "for dataset_row in weave.use(dataset.rows):\n", + " print(predict(dataset_row, {\"shares_skip_chars\": 4}))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "efc63aa7", + "metadata": {}, + "outputs": [], + "source": [ + "fields = [\"name\", \"shares\", \"directors\"]\n", + "\n", + "\n", + "def p_r_f1(tp, fp, fn):\n", + " # if any denom is zero, then zero. could use NaN instead...\n", + " precision = 0\n", + " if tp or fp:\n", + " precision = tp / (tp + fp)\n", + " recall = 0\n", + " if tp or fn:\n", + " recall = tp / (tp + fn)\n", + " f1 = 0\n", + " if precision or recall:\n", + " f1 = 2 * (precision * recall) / (precision + recall)\n", + " return precision, recall, f1\n", + "\n", + "\n", + "def summarize_item(item_result, item_label):\n", + " item_summary = {}\n", + " for f in fields:\n", + " item_summary[f\"{f}_negative\"] = item_result.get(f) is None\n", + " item_summary[f\"{f}_correct\"] = item_result.get(f) == item_label.get(f)\n", + "\n", + " item_correct = sum([item_summary[f\"{f}_correct\"] for f in fields])\n", + " item_tp = sum(\n", + " [item_label.get(f) is not None and item_summary[f\"{f}_correct\"] for f in fields]\n", + " )\n", + " item_fp = sum(\n", + " [\n", + " item_label.get(f) is not None and not item_summary[f\"{f}_correct\"]\n", + " for f in fields\n", + " ]\n", + " )\n", + " item_tn = sum(\n", + " [item_label.get(f) is None and item_summary[f\"{f}_correct\"] for f in fields]\n", + " )\n", + " item_fn = sum(\n", + " [item_label.get(f) is None and not item_summary[f\"{f}_correct\"] for f in fields]\n", + " )\n", + "\n", + " item_precision, item_recall, item_f1 = p_r_f1(item_tp, item_fp, item_fn)\n", + "\n", + " return {\n", + " **item_summary,\n", + " \"correct\": item_correct,\n", + " \"tp\": item_tp,\n", + " \"fp\": item_fp,\n", + " \"tn\": item_tn,\n", + " \"fn\": item_fn,\n", + " \"precision\": item_precision,\n", + " \"recall\": item_recall,\n", + " \"f1\": item_f1,\n", + " }\n", + "\n", + "\n", + "def field_pr(eval_result, field_name):\n", + " tp = sum(\n", + " not item[\"summary\"][f\"{field_name}_negative\"]\n", + " and item[\"summary\"][f\"{field_name}_correct\"]\n", + " for item in eval_result\n", + " )\n", + " fp = sum(\n", + " not item[\"summary\"][f\"{field_name}_negative\"]\n", + " and not item[\"summary\"][f\"{field_name}_correct\"]\n", + " for item in eval_result\n", + " )\n", + " tn = sum(\n", + " item[\"summary\"][f\"{field_name}_negative\"]\n", + " and item[\"summary\"][f\"{field_name}_correct\"]\n", + " for item in eval_result\n", + " )\n", + " fn = sum(\n", + " item[\"summary\"][f\"{field_name}_negative\"]\n", + " and not item[\"summary\"][f\"{field_name}_correct\"]\n", + " for item in eval_result\n", + " )\n", + "\n", + " precision, recall, f1 = p_r_f1(tp, fp, fn)\n", + " return {\n", + " \"tp\": tp,\n", + " \"fp\": fp,\n", + " \"tn\": tn,\n", + " \"fn\": fn,\n", + " \"precision\": precision,\n", + " \"recall\": recall,\n", + " \"f1\": f1,\n", + " }\n", + "\n", + "\n", + "def summarize(eval_result):\n", + " summary = {}\n", + " for field in fields:\n", + " summary[f\"field_{field}\"] = field_pr(eval_result, field)\n", + " for metric in [\"precision\", \"recall\", \"f1\"]:\n", + " summary[f\"avg_{metric}\"] = sum(\n", + " summary[f\"field_{f}\"][metric] for f in fields\n", + " ) / len(fields)\n", + " return summary\n", + "\n", + "\n", + "def evaluate(dataset, predict_config):\n", + " eval_result = []\n", + " correct_count = 0\n", + " count = 0\n", + " for dataset_row in dataset:\n", + " start_time = time.time()\n", + " result = predict(dataset_row, predict_config)\n", + " latency = time.time() - start_time\n", + " latency = random.gauss(\n", + " predict_config[\"latency_mu\"], predict_config[\"latency_sigma\"]\n", + " )\n", + " item_summary = summarize_item(result, dataset_row[\"label\"])\n", + " eval_result.append(\n", + " {\n", + " \"dataset_id\": dataset_row[\"id\"],\n", + " \"result\": result,\n", + " \"summary\": {\"latency\": latency, **item_summary},\n", + " }\n", + " )\n", + " return {\n", + " \"config\": predict_config,\n", + " \"eval_table\": eval_result,\n", + " \"summary\": summarize(eval_result),\n", + " }" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f2248364", + "metadata": {}, + "outputs": [], + "source": [ + "eval_results0 = weave.save(\n", + " evaluate(weave.use(dataset).rows, {\"latency_mu\": 0.3, \"latency_sigma\": 0.1}),\n", + " \"eval_results0\",\n", + ")\n", + "eval_results1 = weave.save(\n", + " evaluate(\n", + " weave.use(dataset).rows,\n", + " {\"latency_mu\": 0.3, \"latency_sigma\": 0.1, \"name_up_to_period\": True},\n", + " ),\n", + " \"eval_results1\",\n", + ")\n", + "eval_results2 = weave.save(\n", + " evaluate(\n", + " weave.use(dataset).rows,\n", + " {\n", + " \"latency_mu\": 0.3,\n", + " \"latency_sigma\": 0.1,\n", + " \"name_up_to_period\": True,\n", + " \"shares_skip_chars\": 4,\n", + " },\n", + " ),\n", + " \"eval_results2\",\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "93727275", + "metadata": {}, + "outputs": [], + "source": [ + "from weave.legacy.weave.panels_py import panel_eval\n", + "\n", + "panel_eval.eval_board(dataset.rows, eval_results0, eval_results2)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "166e8607", + "metadata": {}, + "outputs": [], + "source": [ + "# To add:\n", + "# - backed by W&B runs\n", + "# - show run code / config comparison\n", + "# - show traces of pipelines and compare them\n", + "# - add view of N runs instead of 2" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.7" + } }, - { - "cell_type": "code", - "execution_count": null, - "id": "8172d67c", - "metadata": {}, - "outputs": [], - "source": [ - "raw_labels = {\n", - " \"Articles_of_Incorporation_Real_Example_3_txt\": {\n", - " \"name\": \"HealthFirst Solutions LLC\",\n", - " \"shares\": 500000,\n", - " },\n", - " \"Articles_of_Incorporation_Real_Example_2_txt\": {\n", - " \"name\": \"GreenLeaf LLC\",\n", - " \"shares\": None,\n", - " \"directors\": [\"Sarah Miller\", \"Daniel Lee\"],\n", - " },\n", - " \"Articles_of_Incorporation_Real_Example_1_txt\": {\n", - " \"name\": \"TechBoost Corp\",\n", - " \"shares\": 1000000,\n", - " },\n", - " \"Highly_Varied_Article_of_Incorporation_10_txt\": {\n", - " \"name\": \"Brown, Fernandez and Smith\",\n", - " \"shares\": 41141,\n", - " },\n", - " \"Highly_Varied_Article_of_Incorporation_9_txt\": {\n", - " \"name\": \"Ruiz-Goodman\",\n", - " \"shares\": 31783,\n", - " },\n", - " \"Highly_Varied_Article_of_Incorporation_8_txt\": {\n", - " \"name\": \"Gibson, Hunt and Davidson\",\n", - " \"shares\": 96403,\n", - " },\n", - " \"Highly_Varied_Article_of_Incorporation_7_txt\": {\n", - " \"name\": \"Boyd-Browning\",\n", - " \"shares\": 41300,\n", - " },\n", - " \"Highly_Varied_Article_of_Incorporation_6_txt\": {\n", - " \"name\": \"Newton, Moreno and Yang\",\n", - " \"shares\": 73981,\n", - " },\n", - " \"Highly_Varied_Article_of_Incorporation_5_txt\": {\n", - " \"name\": \"Matthews and Sons\",\n", - " \"shares\": 98608,\n", - " },\n", - " \"Highly_Varied_Article_of_Incorporation_4_txt\": {\n", - " \"name\": \"Moore LLC\",\n", - " \"shares\": 5732,\n", - " },\n", - " \"Highly_Varied_Article_of_Incorporation_3_txt\": {\n", - " \"name\": \"Mullen Inc\",\n", - " \"shares\": 76197,\n", - " },\n", - " \"Highly_Varied_Article_of_Incorporation_2_txt\": {\n", - " \"name\": \"Ellis and Sons\",\n", - " \"shares\": 54183,\n", - " },\n", - " \"Highly_Varied_Article_of_Incorporation_1_txt\": {\n", - " \"name\": \"French, Wyatt and Coleman\",\n", - " \"shares\": 78821,\n", - " },\n", - "}\n", - "\n", - "\n", - "def read_dataset(root):\n", - " dataset_rows = []\n", - " for p in glob.glob(os.path.join(root, \"*.txt\")):\n", - " # Have to do replace here because of weave '.' access issues\n", - " example_id = os.path.basename(p).replace(\".\", \"_\")\n", - " label = raw_labels.get(example_id)\n", - " if label:\n", - " dataset_rows.append(\n", - " {\"id\": example_id, \"example\": open(p).read(), \"label\": label}\n", - " )\n", - " return dataset_rows" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "cb93f2a4", - "metadata": {}, - "outputs": [], - "source": [ - "# Can't just make our own types, server won't deserialize.\n", - "# A fairly easy fix.\n", - "@weave.type()\n", - "class Dataset:\n", - " rows: list[typing.Any]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "92c6535c", - "metadata": {}, - "outputs": [], - "source": [ - "raw_dataset = read_dataset(\"/Users/shawn/datasets/aoi\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "7e803efd", - "metadata": {}, - "outputs": [], - "source": [ - "# raw_dataset" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "fc723706", - "metadata": { - "scrolled": false - }, - "outputs": [], - "source": [ - "dataset = weave.save(Dataset(raw_dataset), \"my_dataset5\")\n", - "# Now, here I really want to make my own labels in the UI immediately.\n", - "# where should the added column go? A new version of this dataset?\n", - "# yeah sure why not.\n", - "# What's missing for editing to be good?\n", - "# - batch editing, ie make a bunch of changes and choose where/how to save" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "4a99df38", - "metadata": {}, - "outputs": [], - "source": [ - "# published = weave.publish(Dataset(raw_dataset), 'weave-flow1/my_dataset1')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "77c71a23", - "metadata": {}, - "outputs": [], - "source": [ - "dataset.rows\n", - "# Here i went to render labels next to dataset.\n", - "# I need access to labels in the notebook memory... would be easy enough to pass in" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "331d88d6", - "metadata": {}, - "outputs": [], - "source": [ - "import re\n", - "\n", - "\n", - "def split_paragraphs(doc):\n", - " lines = [l.strip() for l in doc.split(\"\\n\")]\n", - " stripped_doc = \"\\n\".join(lines)\n", - " return [p.strip() for p in stripped_doc.split(\"\\n\\n\")]\n", - "\n", - "\n", - "def find_first_numeric(s):\n", - " match = re.search(r\"\\d+\", s)\n", - " if match is None:\n", - " return None\n", - " return int(match.group().replace(\",\", \"\"))\n", - "\n", - "\n", - "def predict(dataset_row, config):\n", - " paragraphs = split_paragraphs(dataset_row[\"example\"])\n", - " capital_paragraph = None\n", - " name_paragraph = None\n", - " for p in paragraphs:\n", - " if \"name\" in p.lower():\n", - " name_paragraph = p\n", - " if \"share\" in p.lower():\n", - " capital_paragraph = p\n", - " result = {\n", - " \"name\": None,\n", - " \"shares\": None,\n", - " }\n", - " if capital_paragraph:\n", - " paragraph_start = config.get(\"shares_skip_chars\", 0)\n", - " result[\"shares\"] = find_first_numeric(capital_paragraph[paragraph_start:])\n", - " if name_paragraph:\n", - " match = re.search(r\"is \", name_paragraph)\n", - " if match is not None:\n", - " result[\"name\"] = name_paragraph[match.end() :]\n", - " if result[\"name\"] and config.get(\"name_up_to_period\"):\n", - " match = re.search(r\"\\.\", result[\"name\"])\n", - " if match is not None:\n", - " result[\"name\"] = result[\"name\"][: match.start()]\n", - " return result" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b2378174", - "metadata": {}, - "outputs": [], - "source": [ - "for dataset_row in weave.use(dataset.rows):\n", - " print(predict(dataset_row, {\"shares_skip_chars\": 4}))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "efc63aa7", - "metadata": {}, - "outputs": [], - "source": [ - "fields = [\"name\", \"shares\", \"directors\"]\n", - "\n", - "\n", - "def p_r_f1(tp, fp, fn):\n", - " # if any denom is zero, then zero. could use NaN instead...\n", - " precision = 0\n", - " if tp or fp:\n", - " precision = tp / (tp + fp)\n", - " recall = 0\n", - " if tp or fn:\n", - " recall = tp / (tp + fn)\n", - " f1 = 0\n", - " if precision or recall:\n", - " f1 = 2 * (precision * recall) / (precision + recall)\n", - " return precision, recall, f1\n", - "\n", - "\n", - "def summarize_item(item_result, item_label):\n", - " item_summary = {}\n", - " for f in fields:\n", - " item_summary[f\"{f}_negative\"] = item_result.get(f) is None\n", - " item_summary[f\"{f}_correct\"] = item_result.get(f) == item_label.get(f)\n", - "\n", - " item_correct = sum([item_summary[f\"{f}_correct\"] for f in fields])\n", - " item_tp = sum(\n", - " [item_label.get(f) is not None and item_summary[f\"{f}_correct\"] for f in fields]\n", - " )\n", - " item_fp = sum(\n", - " [\n", - " item_label.get(f) is not None and not item_summary[f\"{f}_correct\"]\n", - " for f in fields\n", - " ]\n", - " )\n", - " item_tn = sum(\n", - " [item_label.get(f) is None and item_summary[f\"{f}_correct\"] for f in fields]\n", - " )\n", - " item_fn = sum(\n", - " [item_label.get(f) is None and not item_summary[f\"{f}_correct\"] for f in fields]\n", - " )\n", - "\n", - " item_precision, item_recall, item_f1 = p_r_f1(item_tp, item_fp, item_fn)\n", - "\n", - " return {\n", - " **item_summary,\n", - " \"correct\": item_correct,\n", - " \"tp\": item_tp,\n", - " \"fp\": item_fp,\n", - " \"tn\": item_tn,\n", - " \"fn\": item_fn,\n", - " \"precision\": item_precision,\n", - " \"recall\": item_recall,\n", - " \"f1\": item_f1,\n", - " }\n", - "\n", - "\n", - "def field_pr(eval_result, field_name):\n", - " tp = sum(\n", - " not item[\"summary\"][f\"{field_name}_negative\"]\n", - " and item[\"summary\"][f\"{field_name}_correct\"]\n", - " for item in eval_result\n", - " )\n", - " fp = sum(\n", - " not item[\"summary\"][f\"{field_name}_negative\"]\n", - " and not item[\"summary\"][f\"{field_name}_correct\"]\n", - " for item in eval_result\n", - " )\n", - " tn = sum(\n", - " item[\"summary\"][f\"{field_name}_negative\"]\n", - " and item[\"summary\"][f\"{field_name}_correct\"]\n", - " for item in eval_result\n", - " )\n", - " fn = sum(\n", - " item[\"summary\"][f\"{field_name}_negative\"]\n", - " and not item[\"summary\"][f\"{field_name}_correct\"]\n", - " for item in eval_result\n", - " )\n", - "\n", - " precision, recall, f1 = p_r_f1(tp, fp, fn)\n", - " return {\n", - " \"tp\": tp,\n", - " \"fp\": fp,\n", - " \"tn\": tn,\n", - " \"fn\": fn,\n", - " \"precision\": precision,\n", - " \"recall\": recall,\n", - " \"f1\": f1,\n", - " }\n", - "\n", - "\n", - "def summarize(eval_result):\n", - " summary = {}\n", - " for field in fields:\n", - " summary[f\"field_{field}\"] = field_pr(eval_result, field)\n", - " for metric in [\"precision\", \"recall\", \"f1\"]:\n", - " summary[f\"avg_{metric}\"] = sum(\n", - " summary[f\"field_{f}\"][metric] for f in fields\n", - " ) / len(fields)\n", - " return summary\n", - "\n", - "\n", - "def evaluate(dataset, predict_config):\n", - " eval_result = []\n", - " correct_count = 0\n", - " count = 0\n", - " for dataset_row in dataset:\n", - " start_time = time.time()\n", - " result = predict(dataset_row, predict_config)\n", - " latency = time.time() - start_time\n", - " latency = random.gauss(\n", - " predict_config[\"latency_mu\"], predict_config[\"latency_sigma\"]\n", - " )\n", - " item_summary = summarize_item(result, dataset_row[\"label\"])\n", - " eval_result.append(\n", - " {\n", - " \"dataset_id\": dataset_row[\"id\"],\n", - " \"result\": result,\n", - " \"summary\": {\"latency\": latency, **item_summary},\n", - " }\n", - " )\n", - " return {\n", - " \"config\": predict_config,\n", - " \"eval_table\": eval_result,\n", - " \"summary\": summarize(eval_result),\n", - " }" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f2248364", - "metadata": {}, - "outputs": [], - "source": [ - "eval_results0 = weave.save(\n", - " evaluate(weave.use(dataset).rows, {\"latency_mu\": 0.3, \"latency_sigma\": 0.1}),\n", - " \"eval_results0\",\n", - ")\n", - "eval_results1 = weave.save(\n", - " evaluate(\n", - " weave.use(dataset).rows,\n", - " {\"latency_mu\": 0.3, \"latency_sigma\": 0.1, \"name_up_to_period\": True},\n", - " ),\n", - " \"eval_results1\",\n", - ")\n", - "eval_results2 = weave.save(\n", - " evaluate(\n", - " weave.use(dataset).rows,\n", - " {\n", - " \"latency_mu\": 0.3,\n", - " \"latency_sigma\": 0.1,\n", - " \"name_up_to_period\": True,\n", - " \"shares_skip_chars\": 4,\n", - " },\n", - " ),\n", - " \"eval_results2\",\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "93727275", - "metadata": {}, - "outputs": [], - "source": [ - "from weave.legacy.panels_py import panel_eval\n", - "\n", - "panel_eval.eval_board(dataset.rows, eval_results0, eval_results2)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "166e8607", - "metadata": {}, - "outputs": [], - "source": [ - "# To add:\n", - "# - backed by W&B runs\n", - "# - show run code / config comparison\n", - "# - show traces of pipelines and compare them\n", - "# - add view of N runs instead of 2" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.7" - } - }, - "nbformat": 4, - "nbformat_minor": 5 + "nbformat": 4, + "nbformat_minor": 5 } diff --git a/weave/legacy/ops_arrow/__init__.py b/weave/legacy/ops_arrow/__init__.py deleted file mode 100644 index 6b954627d8b..00000000000 --- a/weave/legacy/ops_arrow/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -from weave.legacy import context_state as _context_state - -_loading_builtins_token = _context_state.set_loading_built_ins() - -from weave.legacy.arrow.arrow import * -from weave.legacy.arrow.convert import * -from weave.legacy.arrow.list_ import * -from weave.legacy.ops_arrow import ops -from weave.legacy.ops_arrow.list_range import range -from weave.legacy.ops_arrow.vectorize import vectorize - -_context_state.clear_loading_built_ins(_loading_builtins_token) diff --git a/weave/legacy/ops_arrow/ops.py b/weave/legacy/ops_arrow/ops.py deleted file mode 100644 index b51ad06934c..00000000000 --- a/weave/legacy/ops_arrow/ops.py +++ /dev/null @@ -1,11 +0,0 @@ -from weave.legacy.ops_arrow.arraylist_ops import * -from weave.legacy.ops_arrow.boolean import * -from weave.legacy.ops_arrow.convert_ops import * -from weave.legacy.ops_arrow.date import * -from weave.legacy.ops_arrow.dict import * -from weave.legacy.ops_arrow.list_join import * -from weave.legacy.ops_arrow.list_ops import * # type: ignore -from weave.legacy.ops_arrow.number import * -from weave.legacy.ops_arrow.obj import * -from weave.legacy.ops_arrow.ref_ops import * -from weave.legacy.ops_arrow.string import * diff --git a/weave/legacy/ops_primitives/test_list_range.py b/weave/legacy/ops_primitives/test_list_range.py deleted file mode 100644 index ce98b09fbae..00000000000 --- a/weave/legacy/ops_primitives/test_list_range.py +++ /dev/null @@ -1,7 +0,0 @@ -import pytest - -import weave - - -def test_range(): - assert weave.use(weave.legacy.ops.range(0, 3, 1)).to_pylist_tagged() == [0, 1, 2] diff --git a/weave/legacy/panels/__init__.py b/weave/legacy/panels/__init__.py deleted file mode 100644 index 8652aaabea1..00000000000 --- a/weave/legacy/panels/__init__.py +++ /dev/null @@ -1,70 +0,0 @@ -from weave.legacy import context_state as _context_state - -_loading_builtins_token = _context_state.set_loading_built_ins() - -from weave.legacy.panels.panel_auto import * - -# basic -from weave.legacy.panels.panel_basic import * - -# top level board -from weave.legacy.panels.panel_board import Board, BoardPanel, BoardPanelLayout -from weave.legacy.panels.panel_card import Card, CardTab -from weave.legacy.panels.panel_color import Color -from weave.legacy.panels.panel_daterange import DateRange - -# domain -from weave.legacy.panels.panel_domain import * -from weave.legacy.panels.panel_dropdown import Dropdown, DropdownConfig -from weave.legacy.panels.panel_each import Each -from weave.legacy.panels.panel_each_column import EachColumn - -# special -from weave.legacy.panels.panel_expression import * -from weave.legacy.panels.panel_facet import Facet -from weave.legacy.panels.panel_facet_tabs import FacetTabs -from weave.legacy.panels.panel_filter_editor import FilterEditor -from weave.legacy.panels.panel_function_editor import ( - FunctionEditor, - FunctionEditorConfig, -) -from weave.legacy.panels.panel_group import ( - Group, - GroupLayoutFlow, - GroupPanel, - GroupPanelLayout, -) -from weave.legacy.panels.panel_grouping_editor import GroupingEditor - -# Incomplete -from weave.legacy.panels.panel_histogram import * -from weave.legacy.panels.panel_html import PanelHtml - -# layout -from weave.legacy.panels.panel_labeled_item import LabeledItem - -# legacy -from weave.legacy.panels.panel_legacy import * -from weave.legacy.panels.panel_markdown import PanelMarkdown - -# Non-standard editor (todo: update) -from weave.legacy.panels.panel_object_picker import ObjectPicker, ObjectPickerConfig -from weave.legacy.panels.panel_plot import Plot, PlotConstants, Series - -# sidebar specific -from weave.legacy.panels.panel_query import Query, QueryCondition, QueryConfig -from weave.legacy.panels.panel_sections import Sections -from weave.legacy.panels.panel_select import SelectEditor, SelectEditorConfig - -# editors -from weave.legacy.panels.panel_slider import Slider, SliderConfig -from weave.legacy.panels.panel_string import PanelString -from weave.legacy.panels.panel_string_editor import StringEditor -from weave.legacy.panels.panel_table import ColumnDef, Table, TableColumn -from weave.legacy.panels.panel_trace import Trace - -# navigation -from weave.legacy.panels.panel_weavelink import WeaveLink -from weave.legacy.panel import Panel - -_context_state.clear_loading_built_ins(_loading_builtins_token) diff --git a/perf_notebooks/case_when v if_then_else.ipynb b/weave/legacy/perf_notebooks/case_when v if_then_else.ipynb similarity index 100% rename from perf_notebooks/case_when v if_then_else.ipynb rename to weave/legacy/perf_notebooks/case_when v if_then_else.ipynb diff --git a/perf_notebooks/pyarrow joinall.ipynb b/weave/legacy/perf_notebooks/pyarrow joinall.ipynb similarity index 100% rename from perf_notebooks/pyarrow joinall.ipynb rename to weave/legacy/perf_notebooks/pyarrow joinall.ipynb diff --git a/weave/legacy/scripts/clear_cache.py b/weave/legacy/scripts/clear_cache.py index 39a6186efef..b630e019f6d 100644 --- a/weave/legacy/scripts/clear_cache.py +++ b/weave/legacy/scripts/clear_cache.py @@ -1,7 +1,7 @@ import os import time -from weave.legacy import cache +from weave.legacy.weave import cache # Script to run to delete expired caches if __name__ == "__main__": diff --git a/weave/legacy/scripts/syndata.py b/weave/legacy/scripts/syndata.py index c2436a7efd9..6ecbc110363 100644 --- a/weave/legacy/scripts/syndata.py +++ b/weave/legacy/scripts/syndata.py @@ -5,8 +5,8 @@ import pyarrow as pa from scipy.signal import butter, filtfilt -from weave.legacy import util -from ..ops_arrow import ArrowWeaveList +from weave.legacy.weave import util +from weave.legacy.weave.ops_arrow import ArrowWeaveList value_fns: list[typing.Any] = [ lambda steps: steps**2, diff --git a/weave/legacy/scripts/syndata_mon.py b/weave/legacy/scripts/syndata_mon.py index d93d7ccb64d..d615b6eb777 100644 --- a/weave/legacy/scripts/syndata_mon.py +++ b/weave/legacy/scripts/syndata_mon.py @@ -10,7 +10,7 @@ import tqdm from faker import Faker -from weave.legacy.arrow.list_ import ArrowWeaveList, dataframe_to_arrow +from weave.legacy.weave.arrow.list_ import ArrowWeaveList, dataframe_to_arrow # Generate the version schedule diff --git a/weave/legacy/ecosystem/twitter/__init__.py b/weave/legacy/tests/__init__.py similarity index 100% rename from weave/legacy/ecosystem/twitter/__init__.py rename to weave/legacy/tests/__init__.py diff --git a/weave/tests/example_wb_unit_test.py b/weave/legacy/tests/example_wb_unit_test.py similarity index 100% rename from weave/tests/example_wb_unit_test.py rename to weave/legacy/tests/example_wb_unit_test.py diff --git a/weave/tests/legacy/test_access.py b/weave/legacy/tests/test_access.py similarity index 85% rename from weave/tests/legacy/test_access.py rename to weave/legacy/tests/test_access.py index cbcfbb6defe..f15627af727 100644 --- a/weave/tests/legacy/test_access.py +++ b/weave/legacy/tests/test_access.py @@ -1,9 +1,14 @@ import pytest import weave -from weave.legacy import artifact_fs, artifact_local, environment, errors, wandb_api - -from ... import storage +from weave.legacy.weave import ( + artifact_fs, + artifact_local, + environment, + errors, + storage, + wandb_api, +) @pytest.fixture() @@ -20,7 +25,7 @@ def public_env(): def test_access_file(public_env): with pytest.raises(errors.WeaveAccessDeniedError): - weave.use(weave.legacy.ops.local_path("/tmp/bad.json")) + weave.use(weave.legacy.weave.ops.local_path("/tmp/bad.json")) @pytest.mark.parametrize("path", ["..", "/tmp", "//tmp", "//tmp/bad.json", "/tmp/.../"]) diff --git a/weave/tests/legacy/test_api.py b/weave/legacy/tests/test_api.py similarity index 95% rename from weave/tests/legacy/test_api.py rename to weave/legacy/tests/test_api.py index dce30746540..25ca58a9d8b 100644 --- a/weave/tests/legacy/test_api.py +++ b/weave/legacy/tests/test_api.py @@ -1,7 +1,8 @@ import shutil -from ... import api as weave -from ...legacy.show import _show_params +from weave.legacy.weave import api as weave + +from ...legacy.weave.show import _show_params def test_print_save_val(): diff --git a/weave/tests/legacy/test_arrow.py b/weave/legacy/tests/test_arrow.py similarity index 98% rename from weave/tests/legacy/test_arrow.py rename to weave/legacy/tests/test_arrow.py index 889835c9588..b9966221b94 100644 --- a/weave/tests/legacy/test_arrow.py +++ b/weave/legacy/tests/test_arrow.py @@ -7,31 +7,39 @@ import pytest from PIL import Image -from weave.legacy import box, context_state, errors, graph, mappers_arrow, ops +from weave.legacy.weave import api as weave +from weave.legacy.weave import ( + box, + context_state, + errors, + graph, + mappers_arrow, + ops, + storage, + weave_internal, +) # If you're thinking of import vectorize here, don't! Put your # tests in test_arrow_vectorizer.py instead -from weave.legacy import ops_arrow as arrow -from weave.legacy import weave_types as types -from weave.legacy.arrow import constructors -from weave.legacy.arrow.arrow_tags import ( +from weave.legacy.weave import ops_arrow as arrow +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.arrow import constructors +from weave.legacy.weave.arrow.arrow_tags import ( recursively_encode_pyarrow_strings_as_dictionaries, ) -from weave.legacy.language_features.tagging import ( +from weave.legacy.weave.language_features.tagging import ( make_tag_getter_op, tag_store, tagged_value_type, ) -from weave.legacy.op_def import map_type -from weave.legacy.ops_domain import project_ops -from weave.legacy.ops_primitives import list_, make_list -from weave.tests import list_arrow_test_helpers as lath +from weave.legacy.weave.op_def import map_type +from weave.legacy.weave.ops_domain import project_ops +from weave.legacy.weave.ops_primitives import list_, make_list +from .util import list_arrow_test_helpers as lath -from ... import api as weave -from ... import storage, weave_internal -from ...tests import tag_test_util as ttu -from ...tests import weavejs_ops -from ...tests.legacy import test_wb +from weave.legacy.tests.util import tag_test_util as ttu +from weave.legacy.tests.util import weavejs_ops +from . import test_wb _loading_builtins_token = context_state.set_loading_built_ins() # T in `conftest::pre_post_each_test` we set a custom artifact directory for each test for isolation @@ -1308,7 +1316,7 @@ def test_stddev(): def test_join_all_struct_val(): - from weave.legacy import ops_arrow + from weave.legacy.weave import ops_arrow t1 = arrow.to_arrow([{"a": 5, "b": {"c": 6}}]) t2 = arrow.to_arrow([{"a": 9, "b": {"c": 10}}, {"a": 5, "b": {"c": 11}}]) diff --git a/weave/tests/legacy/test_arrow_awl.py b/weave/legacy/tests/test_arrow_awl.py similarity index 91% rename from weave/tests/legacy/test_arrow_awl.py rename to weave/legacy/tests/test_arrow_awl.py index c1949a9cb97..03e857e288f 100644 --- a/weave/tests/legacy/test_arrow_awl.py +++ b/weave/legacy/tests/test_arrow_awl.py @@ -3,12 +3,12 @@ import pytest import weave -import weave.legacy.weave_types as types -from weave.legacy import box, ops_arrow -from weave.legacy.arrow.convert import to_arrow -from weave.legacy.language_features.tagging import tag_store, tagged_value_type +import weave.legacy.weave.weave_types as types +from weave.legacy.weave import box, ops_arrow +from weave.legacy.weave.arrow.convert import to_arrow +from weave.legacy.weave.language_features.tagging import tag_store, tagged_value_type -from ..concrete_tagged_value import ( +from weave.legacy.tests.util.concrete_tagged_value import ( TaggedValue, concrete_from_tagstore, concrete_to_tagstore, diff --git a/weave/tests/legacy/test_arrow_concat.py b/weave/legacy/tests/test_arrow_concat.py similarity index 98% rename from weave/tests/legacy/test_arrow_concat.py rename to weave/legacy/tests/test_arrow_concat.py index 45317b3f39a..4e8f551475a 100644 --- a/weave/tests/legacy/test_arrow_concat.py +++ b/weave/legacy/tests/test_arrow_concat.py @@ -3,9 +3,9 @@ import pytest import weave -from weave.legacy import artifact_local, storage -from weave.legacy.ops_arrow import to_arrow -from weave.legacy.ops_domain import wbmedia +from weave.legacy.weave import artifact_local, storage +from weave.legacy.weave.ops_arrow import to_arrow +from weave.legacy.weave.ops_domain import wbmedia # This is not a valid artifact, but we need one to test. We set _read_dirname # so that the artifact's is_saved property is True, so that everything works diff --git a/weave/tests/legacy/test_arrow_perf.py b/weave/legacy/tests/test_arrow_perf.py similarity index 91% rename from weave/tests/legacy/test_arrow_perf.py rename to weave/legacy/tests/test_arrow_perf.py index 2ee09f152ed..87f7c2c4576 100644 --- a/weave/tests/legacy/test_arrow_perf.py +++ b/weave/legacy/tests/test_arrow_perf.py @@ -5,7 +5,7 @@ import pytest import weave -from weave.legacy import ops_arrow, ops_primitives +from weave.legacy.weave import ops_arrow, ops_primitives @pytest.mark.skip(reason="Performance test") diff --git a/weave/tests/legacy/test_arrow_topy.py b/weave/legacy/tests/test_arrow_topy.py similarity index 98% rename from weave/tests/legacy/test_arrow_topy.py rename to weave/legacy/tests/test_arrow_topy.py index 6f5b2839cf8..59317cc3ce8 100644 --- a/weave/tests/legacy/test_arrow_topy.py +++ b/weave/legacy/tests/test_arrow_topy.py @@ -4,9 +4,9 @@ import pytest import weave -from weave.legacy import ops_arrow +from weave.legacy.weave import ops_arrow -from ..concrete_tagged_value import ( +from weave.legacy.tests.util.concrete_tagged_value import ( TaggedValue, concrete_from_tagstore, concrete_to_tagstore, diff --git a/weave/tests/legacy/test_arrow_vectorizer.py b/weave/legacy/tests/test_arrow_vectorizer.py similarity index 98% rename from weave/tests/legacy/test_arrow_vectorizer.py rename to weave/legacy/tests/test_arrow_vectorizer.py index 04e1b0f9862..fc920d14c48 100644 --- a/weave/tests/legacy/test_arrow_vectorizer.py +++ b/weave/legacy/tests/test_arrow_vectorizer.py @@ -5,21 +5,19 @@ import pytest from pyarrow import compute as pc -from weave.legacy import box, dispatch, errors, ops -from weave.legacy import ops_arrow as arrow -from weave.legacy import weave_types as types -from weave.legacy.language_features.tagging import ( +from weave.legacy.weave import api as weave +from weave.legacy.weave import box, dispatch, errors, ops, weave_internal +from weave.legacy.weave import ops_arrow as arrow +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.language_features.tagging import ( make_tag_getter_op, tag_store, tagged_value_type, ) -from weave.legacy.ops_arrow import arraylist_ops, convert_ops, util -from weave.legacy.ops_domain import run_ops -from weave.legacy.ops_domain import wb_domain_types as wdt -from weave.legacy.ops_primitives import Boolean, Number, date, dict_, list_ - -from ... import api as weave -from ... import weave_internal +from weave.legacy.weave.ops_arrow import arraylist_ops, convert_ops, util +from weave.legacy.weave.ops_domain import run_ops +from weave.legacy.weave.ops_domain import wb_domain_types as wdt +from weave.legacy.weave.ops_primitives import Boolean, Number, date, dict_, list_ string_ops_test_cases = [ ("eq-scalar", lambda x: x == "bc", [True, False, False]), diff --git a/weave/tests/legacy/test_artifact.py b/weave/legacy/tests/test_artifact.py similarity index 95% rename from weave/tests/legacy/test_artifact.py rename to weave/legacy/tests/test_artifact.py index 27cde9e1e1b..5a05479a9d0 100644 --- a/weave/tests/legacy/test_artifact.py +++ b/weave/legacy/tests/test_artifact.py @@ -1,10 +1,8 @@ import pytest import weave -from weave.legacy import artifact_fs, artifact_local -from weave.legacy import ops_arrow as arrow - -from ... import storage +from weave.legacy.weave import artifact_fs, artifact_local, storage +from weave.legacy.weave import ops_arrow as arrow def test_artifact(): diff --git a/weave/tests/legacy/test_artifact_metadata.py b/weave/legacy/tests/test_artifact_metadata.py similarity index 91% rename from weave/tests/legacy/test_artifact_metadata.py rename to weave/legacy/tests/test_artifact_metadata.py index 30e8812d4dd..889eef4137b 100644 --- a/weave/tests/legacy/test_artifact_metadata.py +++ b/weave/legacy/tests/test_artifact_metadata.py @@ -1,8 +1,8 @@ import wandb import weave -from weave.legacy import artifact_local, artifact_wandb -from weave.legacy.wandb_interface.wandb_artifact_pusher import ( +from weave.legacy.weave import artifact_local, artifact_wandb +from weave.legacy.weave.wandb_interface.wandb_artifact_pusher import ( write_artifact_to_wandb, ) @@ -56,8 +56,8 @@ def test_artifact_metadata(user_by_api_key_in_env): } # Push an artifact to wandb and verify that the metadata is correct - remote_uri = weave.legacy.ops.publish_artifact( - weave.legacy.ops.get(local_art.uri + "/obj"), + remote_uri = weave.legacy.weave.ops.publish_artifact( + weave.legacy.weave.ops.get(local_art.uri + "/obj"), "test_artifact", "test_project", None, @@ -113,7 +113,7 @@ def test_artifact_files_count(user_by_api_key_in_env): run.finish() count_node = ( - weave.legacy.ops.project(run.entity, run.project) + weave.legacy.weave.ops.project(run.entity, run.project) .artifact("test") .membershipForAlias("v0") .artifactVersion() diff --git a/weave/tests/legacy/test_assign_perf.py b/weave/legacy/tests/test_assign_perf.py similarity index 100% rename from weave/tests/legacy/test_assign_perf.py rename to weave/legacy/tests/test_assign_perf.py diff --git a/weave/tests/legacy/test_assignment.py b/weave/legacy/tests/test_assignment.py similarity index 96% rename from weave/tests/legacy/test_assignment.py rename to weave/legacy/tests/test_assignment.py index dfc10703f07..6f321857939 100644 --- a/weave/tests/legacy/test_assignment.py +++ b/weave/legacy/tests/test_assignment.py @@ -7,7 +7,7 @@ @pytest.mark.parametrize("type_name, type_cls", [(t.name, t) for t in types]) def test_const_assignment(type_name, type_cls): - from weave.legacy.ops_domain import wb_domain_types as wdt + from weave.legacy.weave.ops_domain import wb_domain_types as wdt params = [] # Quick list of types that don't work with this parametrization diff --git a/weave/tests/legacy/test_async.py b/weave/legacy/tests/test_async.py similarity index 97% rename from weave/tests/legacy/test_async.py rename to weave/legacy/tests/test_async.py index 976e7e9398d..a7601ceda3e 100644 --- a/weave/tests/legacy/test_async.py +++ b/weave/legacy/tests/test_async.py @@ -1,8 +1,6 @@ import pytest -from weave.legacy import async_demo, ops, runs - -from ... import api, storage +from weave.legacy.weave import api, async_demo, ops, runs, storage def test_run_basic(): diff --git a/weave/tests/legacy/test_async_queue.py b/weave/legacy/tests/test_async_queue.py similarity index 94% rename from weave/tests/legacy/test_async_queue.py rename to weave/legacy/tests/test_async_queue.py index 18c37511e38..5556f89b82a 100644 --- a/weave/tests/legacy/test_async_queue.py +++ b/weave/legacy/tests/test_async_queue.py @@ -4,7 +4,7 @@ import aioprocessing import pytest -from weave.legacy.async_queue import ProcessQueue, Queue, ThreadQueue +from weave.legacy.weave.async_queue import ProcessQueue, Queue, ThreadQueue async def process_producer(queue: Queue) -> None: diff --git a/weave/tests/legacy/test_basic_ops.py b/weave/legacy/tests/test_basic_ops.py similarity index 95% rename from weave/tests/legacy/test_basic_ops.py rename to weave/legacy/tests/test_basic_ops.py index c957d907e0e..a40920b967d 100644 --- a/weave/tests/legacy/test_basic_ops.py +++ b/weave/legacy/tests/test_basic_ops.py @@ -1,9 +1,8 @@ -from weave.legacy import box, ops -from weave.legacy.ops_primitives import number -from weave.legacy.ops_primitives.string import * - -from ... import api as weave -from ...weave_internal import make_const_node +from weave.legacy.weave import api as weave +from weave.legacy.weave import box, ops +from weave.legacy.weave.ops_primitives import number +from weave.legacy.weave.ops_primitives.string import * +from weave.legacy.weave.weave_internal import make_const_node def test_number_ops(): diff --git a/weave/tests/legacy/test_box.py b/weave/legacy/tests/test_box.py similarity index 79% rename from weave/tests/legacy/test_box.py rename to weave/legacy/tests/test_box.py index 22a596cbdaf..4ece9ef3949 100644 --- a/weave/tests/legacy/test_box.py +++ b/weave/legacy/tests/test_box.py @@ -1,6 +1,6 @@ import datetime -from weave.legacy import box +from weave.legacy.weave import box def test_boxdatetime(): diff --git a/weave/tests/legacy/test_cache.py b/weave/legacy/tests/test_cache.py similarity index 98% rename from weave/tests/legacy/test_cache.py rename to weave/legacy/tests/test_cache.py index 0fe6243504c..7f46a6723cb 100644 --- a/weave/tests/legacy/test_cache.py +++ b/weave/legacy/tests/test_cache.py @@ -2,7 +2,7 @@ import os import time -from weave.legacy import cache, environment +from weave.legacy.weave import cache, environment def test_lru_time_window_cache(): diff --git a/weave/tests/legacy/test_codify.py b/weave/legacy/tests/test_codify.py similarity index 71% rename from weave/tests/legacy/test_codify.py rename to weave/legacy/tests/test_codify.py index 9d73a391f7c..fdffd2e4e51 100644 --- a/weave/tests/legacy/test_codify.py +++ b/weave/legacy/tests/test_codify.py @@ -2,7 +2,7 @@ import pytest import weave -from weave.legacy import panels +from weave.legacy.weave import panels # IMPORTANT: Do not import other symbols inside of weave # so that we ensure the produced code only relies on the weave symbol. @@ -19,24 +19,24 @@ ), ( panels.Group(), - "weave.legacy.panels.panel_group.Group()", + "weave.legacy.weave.panels.panel_group.Group()", ), ( lambda: panels.Table( - weave.legacy.ops.range(1, 100, 1).map( - lambda row: weave.legacy.ops_primitives.dict.dict_( + weave.legacy.weave.ops.range(1, 100, 1).map( + lambda row: weave.legacy.weave.ops_primitives.dict.dict_( x=row, - y=weave.legacy.ops_primitives.list_.make_list( + y=weave.legacy.weave.ops_primitives.list_.make_list( a=row, ), ) ) ), - lambda: """weave.legacy.panels.panel_table.Table( - weave.legacy.ops_arrow.list_range.range(1, 100, 1,).map( - lambda row: weave.legacy.ops_primitives.dict.dict_( + lambda: """weave.legacy.weave.panels.panel_table.Table( + weave.legacy.weave.ops_arrow.list_range.range(1, 100, 1,).map( + lambda row: weave.legacy.weave.ops_primitives.dict.dict_( x=row, - y=weave.legacy.ops_primitives.list_.make_list( + y=weave.legacy.weave.ops_primitives.list_.make_list( a=row, ), ), @@ -45,16 +45,16 @@ ), ( lambda: panels.Plot( - weave.legacy.ops.range(1, 100, 1).map( - lambda row: weave.legacy.ops_primitives.dict.dict_( + weave.legacy.weave.ops.range(1, 100, 1).map( + lambda row: weave.legacy.weave.ops_primitives.dict.dict_( x=row, y=row**2, ) ) ), - lambda: """weave.legacy.panels.panel_plot.Plot( - weave.legacy.ops_arrow.list_range.range(1, 100, 1,).map( - lambda row: weave.legacy.ops_primitives.dict.dict_( + lambda: """weave.legacy.weave.panels.panel_plot.Plot( + weave.legacy.weave.ops_arrow.list_range.range(1, 100, 1,).map( + lambda row: weave.legacy.weave.ops_primitives.dict.dict_( x=row, y=row.powBinary(2,), ), @@ -65,7 +65,7 @@ lambda: panels.Group( items={ "table": panels.Table( - weave.legacy.ops.range(1, 100, 1), + weave.legacy.weave.ops.range(1, 100, 1), columns=[ lambda row: row, lambda row: row**2, @@ -99,26 +99,26 @@ ), } ), - lambda: """weave.legacy.panels.panel_group.Group( + lambda: """weave.legacy.weave.panels.panel_group.Group( items={ - "table": weave.legacy.panels.panel_table.Table( - weave.legacy.ops_arrow.list_range.range(1, 100, 1,), + "table": weave.legacy.weave.panels.panel_table.Table( + weave.legacy.weave.ops_arrow.list_range.range(1, 100, 1,), columns=[ lambda row: row, lambda row: row.powBinary(2,), ], ), - "all_rows": lambda table: weave.legacy.panels.panel_plot.Plot( + "all_rows": lambda table: weave.legacy.weave.panels.panel_plot.Plot( table.all_rows(), x=lambda row: row["c_0"], y=lambda row: row["c_1"], ), - "derived": lambda table, all_rows: weave.legacy.panels.panel_group.Group( + "derived": lambda table, all_rows: weave.legacy.weave.panels.panel_group.Group( layoutMode="horizontal", items={ - "rows": weave.legacy.panels.panel_group.Group( + "rows": weave.legacy.weave.panels.panel_group.Group( items={ - "pinned_rows": weave.legacy.panels.panel_plot.Plot( + "pinned_rows": weave.legacy.weave.panels.panel_plot.Plot( table.pinned_rows(), x=lambda row: row["c_0"], y=lambda row: row["c_1"], @@ -126,7 +126,7 @@ "active_row": lambda pinned_rows: table.active_row(), }, ), - "data": lambda rows: weave.legacy.panels.panel_group.Group( + "data": lambda rows: weave.legacy.weave.panels.panel_group.Group( items={ "pinned_data": table.pinned_data(), "active_data": lambda pinned_data: table.active_data(), @@ -153,7 +153,7 @@ def test_group_case(cereal_csv, consistent_table_col_ids): panels.Group( items={ "plot": panels.Plot( - weave.legacy.ops.local_path(cereal_csv).readcsv(), + weave.legacy.weave.ops.local_path(cereal_csv).readcsv(), x=lambda row: row["protein"], y=lambda row: row["calories"], ), @@ -166,16 +166,16 @@ def test_group_case(cereal_csv, consistent_table_col_ids): ), } ), - '''weave.legacy.panels.panel_group.Group( + '''weave.legacy.weave.panels.panel_group.Group( items={ - "plot": weave.legacy.panels.panel_plot.Plot( - weave.legacy.ops.local_path("''' + "plot": weave.legacy.weave.panels.panel_plot.Plot( + weave.legacy.weave.ops.local_path("''' + cereal_csv + """",).readcsv(), x=lambda row: row["protein"], y=lambda row: row["calories"], ), - "table": lambda plot: weave.legacy.panels.panel_table.Table( + "table": lambda plot: weave.legacy.weave.panels.panel_table.Table( plot.selected_rows(), columns=[ lambda row: row["c_0"], @@ -191,12 +191,12 @@ def test_group_case(cereal_csv, consistent_table_col_ids): def test_plot_case(cereal_csv, consistent_table_col_ids): _test_object_codification( panels.Plot( - weave.legacy.ops.local_path(cereal_csv).readcsv(), + weave.legacy.weave.ops.local_path(cereal_csv).readcsv(), x=lambda row: row["protein"], y=lambda row: row["calories"], ), - f"""weave.legacy.panels.panel_plot.Plot( - weave.legacy.ops.local_path('{cereal_csv}',).readcsv(), + f"""weave.legacy.weave.panels.panel_plot.Plot( + weave.legacy.weave.ops.local_path('{cereal_csv}',).readcsv(), x=lambda row: row["protein"], y=lambda row: row["calories"], )""", @@ -206,14 +206,14 @@ def test_plot_case(cereal_csv, consistent_table_col_ids): def test_table_case(cereal_csv, consistent_table_col_ids): _test_object_codification( panels.Table( - weave.legacy.ops.local_path(cereal_csv).readcsv(), + weave.legacy.weave.ops.local_path(cereal_csv).readcsv(), columns=[ lambda row: row["protein"], lambda row: row["calories"], ], ), - f"""weave.legacy.panels.panel_table.Table( - weave.legacy.ops.local_path('{cereal_csv}',).readcsv(), + f"""weave.legacy.weave.panels.panel_table.Table( + weave.legacy.weave.ops.local_path('{cereal_csv}',).readcsv(), columns=[ lambda row: row["protein"], lambda row: row["calories"], @@ -223,7 +223,7 @@ def test_table_case(cereal_csv, consistent_table_col_ids): def _test_object_codification(panel, code=None): - panel_code = weave.legacy.codify.object_to_code(panel) + panel_code = weave.legacy.weave.codify.object_to_code(panel) generated_panel = eval(panel_code) diff --git a/weave/tests/legacy/test_compile.py b/weave/legacy/tests/test_compile.py similarity index 87% rename from weave/tests/legacy/test_compile.py rename to weave/legacy/tests/test_compile.py index ef0ec1014bd..6e1895eff25 100644 --- a/weave/tests/legacy/test_compile.py +++ b/weave/legacy/tests/test_compile.py @@ -2,15 +2,14 @@ import wandb import weave -from weave.legacy import async_demo, compile, graph -from weave.legacy import weave_types as types -from weave.legacy.dispatch import RuntimeOutputNode -from weave.legacy.ops_arrow import to_arrow -from weave.legacy.ops_arrow.vectorize import raise_on_python_bailout -from weave.legacy.wandb_interface.wandb_stream_table import StreamTable -from weave.weave_internal import const, define_fn, make_const_node - -from ...api import use +from weave.legacy.weave import async_demo, compile, graph +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.api import use +from weave.legacy.weave.dispatch import RuntimeOutputNode +from weave.legacy.weave.ops_arrow import to_arrow +from weave.legacy.weave.ops_arrow.vectorize import raise_on_python_bailout +from weave.legacy.weave.wandb_interface.wandb_stream_table import StreamTable +from weave.legacy.weave.weave_internal import const, define_fn, make_const_node def test_automatic_await_compile(): @@ -145,21 +144,21 @@ def test_executing_js_multi_root(): def test_optimize_merge_empty_dict(): - non_empty_dict = weave.legacy.ops.dict_(a=5, b=2) + non_empty_dict = weave.legacy.weave.ops.dict_(a=5, b=2) assert ( compile.compile_simple_optimizations( - [weave.legacy.ops.TypedDict.merge(non_empty_dict, weave.legacy.ops.dict_())] + [weave.legacy.weave.ops.TypedDict.merge(non_empty_dict, weave.legacy.weave.ops.dict_())] )[0].to_json() == non_empty_dict.to_json() ) assert ( compile.compile_simple_optimizations( - [weave.legacy.ops.TypedDict.merge(weave.legacy.ops.dict_(), non_empty_dict)] + [weave.legacy.weave.ops.TypedDict.merge(weave.legacy.weave.ops.dict_(), non_empty_dict)] )[0].to_json() == non_empty_dict.to_json() ) - non_simplified_merge = weave.legacy.ops.TypedDict.merge( - weave.legacy.ops.dict_(j=3), non_empty_dict + non_simplified_merge = weave.legacy.weave.ops.TypedDict.merge( + weave.legacy.weave.ops.dict_(j=3), non_empty_dict ) assert ( compile.compile_simple_optimizations([non_simplified_merge])[0].to_json() @@ -168,12 +167,12 @@ def test_optimize_merge_empty_dict(): def test_compile_lambda_uniqueness(): - list_node_1 = weave.legacy.ops.make_list(a=make_const_node(weave.types.Number(), 1)) - list_node_2 = weave.legacy.ops.make_list(a=make_const_node(weave.types.Number(), 2)) + list_node_1 = weave.legacy.weave.ops.make_list(a=make_const_node(weave.types.Number(), 1)) + list_node_2 = weave.legacy.weave.ops.make_list(a=make_const_node(weave.types.Number(), 2)) fn_node = define_fn({"row": weave.types.Number()}, lambda row: row + 1) mapped_1 = list_node_1.map(fn_node) mapped_2 = list_node_2.map(fn_node) - combined = weave.legacy.ops.make_list(a=mapped_1, b=mapped_2) + combined = weave.legacy.weave.ops.make_list(a=mapped_1, b=mapped_2) concatted = combined.concat() # list node contains 2 nodes (const, list), x 2 = 4 @@ -205,7 +204,7 @@ def test_compile_lambda_uniqueness(): # This test demonstrates successful execution when there is an explicit # const function instead of a direct node (resulting in an intermediate execution op) # """ -# history_node = weave.legacy.ops.project(run.entity, run.project).run(run.id).history2() +# history_node = weave.legacy.weave.ops.project(run.entity, run.project).run(run.id).history2() # pick = const(history_node).pick("val") # res = weave.use(pick) # assert res.to_pylist_notags() == list(range(10)) @@ -224,7 +223,7 @@ def test_compile_through_function_call(user_by_api_key_in_env): fn_node = define_fn( {"entity_name": types.String()}, lambda entity_name: ( - weave.legacy.ops.project(entity_name, run.project).run(run.id).history2() + weave.legacy.weave.ops.project(entity_name, run.project).run(run.id).history2() ), ) called_node = fn_node(run.entity) @@ -238,7 +237,7 @@ def test_compile_list_flatten_to_awl_concat(): # When the outer list-structure is a list, we want to dispatch to concat, preferably AWL-concat # when the outer list-structure is an AWL, we want to dispatch ensure that we use AWL ops # list of lists - list_list_node = weave.legacy.ops.make_list(a=[1], b=[2]) + list_list_node = weave.legacy.weave.ops.make_list(a=[1], b=[2]) list_list_node_concat = list_list_node.concat() list_list_node_flatten = list_list_node.flatten() list_list_node_concat_compiled = compile.compile([list_list_node_concat])[0] @@ -246,7 +245,7 @@ def test_compile_list_flatten_to_awl_concat(): assert list_list_node_concat_compiled.from_op.name == "concat" assert list_list_node_flatten_compiled.from_op.name == "flatten" # list of awls - list_awl_node = weave.legacy.ops.make_list(a=to_arrow([1]), b=to_arrow([2])) + list_awl_node = weave.legacy.weave.ops.make_list(a=to_arrow([1]), b=to_arrow([2])) list_awl_node_concat = list_awl_node.concat() list_awl_node_flatten = list_awl_node.flatten() list_awl_node_concat_compiled = compile.compile([list_awl_node_concat])[0] diff --git a/weave/tests/legacy/test_complex_calls.py b/weave/legacy/tests/test_complex_calls.py similarity index 91% rename from weave/tests/legacy/test_complex_calls.py rename to weave/legacy/tests/test_complex_calls.py index 07fe79a5f01..94bebf4cf9f 100644 --- a/weave/tests/legacy/test_complex_calls.py +++ b/weave/legacy/tests/test_complex_calls.py @@ -1,6 +1,5 @@ import weave - -from ... import weave_internal +from weave.legacy.weave import weave_internal def test_weave_fn_in_data(): diff --git a/weave/tests/legacy/test_cond.py b/weave/legacy/tests/test_cond.py similarity index 68% rename from weave/tests/legacy/test_cond.py rename to weave/legacy/tests/test_cond.py index be9b7888e45..f6030535c74 100644 --- a/weave/tests/legacy/test_cond.py +++ b/weave/legacy/tests/test_cond.py @@ -1,12 +1,12 @@ import weave -from weave.legacy import ops_arrow +from weave.legacy.weave import ops_arrow def test_cond_basic(): - assert weave.use(weave.legacy.ops.cond({"a": True}, {"a": 5})) == 5 - assert weave.use(weave.legacy.ops.cond({"a": False}, {"a": 5})) == None + assert weave.use(weave.legacy.weave.ops.cond({"a": True}, {"a": 5})) == 5 + assert weave.use(weave.legacy.weave.ops.cond({"a": False}, {"a": 5})) == None assert ( - weave.use(weave.legacy.ops.cond({"a": False, "b": True}, {"a": 5, "b": 6})) == 6 + weave.use(weave.legacy.weave.ops.cond({"a": False, "b": True}, {"a": 5, "b": 6})) == 6 ) @@ -47,9 +47,9 @@ def test_cond_vector(): ) assert weave.use( conds.map( - lambda row: weave.legacy.ops.cond( - weave.legacy.ops.dict_(**{"a": row["a"], "b": row["b"]}), - weave.legacy.ops.dict_(**{"a": row["val_a"], "b": row["val_b"]}), + lambda row: weave.legacy.weave.ops.cond( + weave.legacy.weave.ops.dict_(**{"a": row["a"], "b": row["b"]}), + weave.legacy.weave.ops.dict_(**{"a": row["val_a"], "b": row["val_b"]}), ) ) ).to_pylist_raw() == [5, None, 10] @@ -68,9 +68,9 @@ def test_cond_vector_arr_value(): ) assert weave.use( conds.map( - lambda row: weave.legacy.ops.cond( - weave.legacy.ops.dict_(**{"a": row["a"], "b": row["b"]}), - weave.legacy.ops.dict_(**{"a": row["val_a"], "b": row["val_b"]}), + lambda row: weave.legacy.weave.ops.cond( + weave.legacy.weave.ops.dict_(**{"a": row["a"], "b": row["b"]}), + weave.legacy.weave.ops.dict_(**{"a": row["val_a"], "b": row["val_b"]}), ) ) ).to_pylist_raw() == [[1, 2], None, [11, 12]] @@ -89,9 +89,9 @@ def test_cond_vector_mixed(): ) assert weave.use( conds.map( - lambda row: weave.legacy.ops.cond( - weave.legacy.ops.dict_(**{"a": row["a"], "b": row["b"]}), - weave.legacy.ops.dict_(**{"a": row["val_a"], "b": 99}), + lambda row: weave.legacy.weave.ops.cond( + weave.legacy.weave.ops.dict_(**{"a": row["a"], "b": row["b"]}), + weave.legacy.weave.ops.dict_(**{"a": row["val_a"], "b": 99}), ) ) ).to_pylist_raw() == [1, None, 99] @@ -110,9 +110,9 @@ def test_cond_vector_mixed_arr_value(): ) assert weave.use( conds.map( - lambda row: weave.legacy.ops.cond( - weave.legacy.ops.dict_(**{"a": row["a"], "b": row["b"]}), - weave.legacy.ops.dict_(**{"a": row["val_a"], "b": [99, 100]}), + lambda row: weave.legacy.weave.ops.cond( + weave.legacy.weave.ops.dict_(**{"a": row["a"], "b": row["b"]}), + weave.legacy.weave.ops.dict_(**{"a": row["val_a"], "b": [99, 100]}), ) ) ).to_pylist_raw() == [[1, 2], None, [99, 100]] diff --git a/weave/tests/legacy/test_const_type_mapper.py b/weave/legacy/tests/test_const_type_mapper.py similarity index 89% rename from weave/tests/legacy/test_const_type_mapper.py rename to weave/legacy/tests/test_const_type_mapper.py index c6143cdc278..ce2fccef3c0 100644 --- a/weave/tests/legacy/test_const_type_mapper.py +++ b/weave/legacy/tests/test_const_type_mapper.py @@ -1,9 +1,7 @@ import typing import weave -from weave.legacy import context_state - -from ... import weave_internal +from weave.legacy.weave import context_state, weave_internal _loading_builtins_token = context_state.set_loading_built_ins() diff --git a/weave/tests/legacy/test_custom_types.py b/weave/legacy/tests/test_custom_types.py similarity index 90% rename from weave/tests/legacy/test_custom_types.py rename to weave/legacy/tests/test_custom_types.py index 5f8940785fa..75a1aaeefdc 100644 --- a/weave/tests/legacy/test_custom_types.py +++ b/weave/legacy/tests/test_custom_types.py @@ -1,12 +1,12 @@ import pytest from PIL import Image -from weave.legacy import context_state as _context -from weave.legacy import ops_arrow +from weave.legacy.weave import api as weave +from weave.legacy.weave import context_state as _context +from weave.legacy.weave import ops_arrow -from ... import api as weave from ... import errors -from .. import geom +from weave.legacy.tests.util import geom def test_mapped_method_on_custom_type(): diff --git a/weave/tests/legacy/test_datetime_timestamp.py b/weave/legacy/tests/test_datetime_timestamp.py similarity index 96% rename from weave/tests/legacy/test_datetime_timestamp.py rename to weave/legacy/tests/test_datetime_timestamp.py index d878cff1d19..c53fc6c38ec 100644 --- a/weave/tests/legacy/test_datetime_timestamp.py +++ b/weave/legacy/tests/test_datetime_timestamp.py @@ -4,7 +4,7 @@ import pytest import weave -from weave.legacy.ops_primitives import date +from weave.legacy.weave.ops_primitives import date def assert_date_string( diff --git a/weave/tests/legacy/test_decorator_type.py b/weave/legacy/tests/test_decorator_type.py similarity index 100% rename from weave/tests/legacy/test_decorator_type.py rename to weave/legacy/tests/test_decorator_type.py diff --git a/weave/tests/legacy/test_decorators.py b/weave/legacy/tests/test_decorators.py similarity index 91% rename from weave/tests/legacy/test_decorators.py rename to weave/legacy/tests/test_decorators.py index e47c79db7a9..72ec915a0ae 100644 --- a/weave/tests/legacy/test_decorators.py +++ b/weave/legacy/tests/test_decorators.py @@ -1,8 +1,7 @@ -from weave.legacy import weave_types as types -from weave.legacy.decorator_op import op - -from ... import api as weave -from ... import storage +from weave.legacy.weave import api as weave +from weave.legacy.weave import storage +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.decorator_op import op def test_function_op_name(): diff --git a/weave/tests/legacy/test_derive_op.py b/weave/legacy/tests/test_derive_op.py similarity index 65% rename from weave/tests/legacy/test_derive_op.py rename to weave/legacy/tests/test_derive_op.py index 85b86659543..ee3dc7f6c03 100644 --- a/weave/tests/legacy/test_derive_op.py +++ b/weave/legacy/tests/test_derive_op.py @@ -1,5 +1,6 @@ -from ... import api as weave -from ...legacy import registry_mem +from weave.legacy.weave import api as weave + +from ...legacy.weave import registry_mem def test_mapped_add(): diff --git a/weave/tests/legacy/test_dispatch.py b/weave/legacy/tests/test_dispatch.py similarity index 88% rename from weave/tests/legacy/test_dispatch.py rename to weave/legacy/tests/test_dispatch.py index 253fb3a8b2c..b9c3eacd833 100644 --- a/weave/tests/legacy/test_dispatch.py +++ b/weave/legacy/tests/test_dispatch.py @@ -1,7 +1,7 @@ import pytest import weave -from weave.legacy import ops_arrow +from weave.legacy.weave import ops_arrow def test_dispatch_lambda(): diff --git a/weave/tests/legacy/test_examples.py b/weave/legacy/tests/test_examples.py similarity index 88% rename from weave/tests/legacy/test_examples.py rename to weave/legacy/tests/test_examples.py index b9324c781f8..01b390526e3 100644 --- a/weave/tests/legacy/test_examples.py +++ b/weave/legacy/tests/test_examples.py @@ -1,9 +1,8 @@ import math import typing -from weave.legacy import context, context_state - -from ... import api as weave +from weave.legacy.weave import api as weave +from weave.legacy.weave import context, context_state class XOnly(typing.TypedDict): diff --git a/weave/tests/legacy/test_execute.py b/weave/legacy/tests/test_execute.py similarity index 93% rename from weave/tests/legacy/test_execute.py rename to weave/legacy/tests/test_execute.py index 29a8a93191f..bb8c4d73e9f 100644 --- a/weave/tests/legacy/test_execute.py +++ b/weave/legacy/tests/test_execute.py @@ -4,15 +4,14 @@ import pytest import weave -from weave.legacy import environment, execute, ops -from weave.legacy import weave_types as types +from weave.legacy.weave import api, environment, execute, ops, weave_internal +from weave.legacy.weave import weave_types as types -from ... import api, weave_internal from . import test_wb execute_test_count_op_run_count = 0 -from weave.legacy import context_state as _context_state +from weave.legacy.weave import context_state as _context_state _loading_builtins_token = _context_state.set_loading_built_ins() @@ -174,12 +173,12 @@ def test_cache_column(): expected_result = [{"x": x, "y": x + 10000} for x in input_vals] l = weave.save(input_vals) - mapped = l.map(lambda x: weave.legacy.ops.dict_(x=x, y=expensive_op(x))) + mapped = l.map(lambda x: weave.legacy.weave.ops.dict_(x=x, y=expensive_op(x))) res = weave.use(mapped) assert res == expected_result latest_obj = weave.use( - weave.legacy.ops.get("local-artifact:///run-op-expensive_op:latest/obj") + weave.legacy.weave.ops.get("local-artifact:///run-op-expensive_op:latest/obj") ) assert len(latest_obj) == len(input_vals) assert len(weave.versions(latest_obj)) == 1 @@ -198,7 +197,7 @@ def test_none_not_cached(): assert res == expected_result latest_obj = weave.use( - weave.legacy.ops.get("local-artifact:///run-op-expensive_op:latest/obj") + weave.legacy.weave.ops.get("local-artifact:///run-op-expensive_op:latest/obj") ) assert len(latest_obj) == 1 # not 2! None not cached! assert len(weave.versions(latest_obj)) == 1 diff --git a/weave/tests/legacy/test_execute_fast.py b/weave/legacy/tests/test_execute_fast.py similarity index 82% rename from weave/tests/legacy/test_execute_fast.py rename to weave/legacy/tests/test_execute_fast.py index 716651dd943..01d26d43b18 100644 --- a/weave/tests/legacy/test_execute_fast.py +++ b/weave/legacy/tests/test_execute_fast.py @@ -1,9 +1,8 @@ import weave -from weave.legacy import dispatch -from weave.legacy import weave_types as types +from weave.legacy.weave import dispatch, weave_internal +from weave.legacy.weave import weave_types as types -from ... import weave_internal -from .. import weavejs_ops +from weave.legacy.tests.util import weavejs_ops def test_nested_weavejs_call(): @@ -36,7 +35,7 @@ def test_empty_list(): map_fn = weave_internal.define_fn( {"row": arr.type.object_type}, lambda row: row.merge( - weave.legacy.ops.dict_(output_classid=row["output_class"].id()) + weave.legacy.weave.ops.dict_(output_classid=row["output_class"].id()) ), ) diff --git a/weave/tests/legacy/test_execution_graphs.py b/weave/legacy/tests/test_execution_graphs.py similarity index 95% rename from weave/tests/legacy/test_execution_graphs.py rename to weave/legacy/tests/test_execution_graphs.py index 37c6caee650..cf7faf64b8a 100644 --- a/weave/tests/legacy/test_execution_graphs.py +++ b/weave/legacy/tests/test_execution_graphs.py @@ -3,8 +3,8 @@ import os import zlib -from weave.legacy import serialize, storage -from weave.legacy.server import handle_request +from weave.legacy.weave import serialize, storage +from weave.legacy.weave.server import handle_request def test_graph_playback(dev_only_admin_env_override): diff --git a/weave/tests/legacy/test_file.py b/weave/legacy/tests/test_file.py similarity index 85% rename from weave/tests/legacy/test_file.py rename to weave/legacy/tests/test_file.py index 0fed90136c3..e1b8299c419 100644 --- a/weave/tests/legacy/test_file.py +++ b/weave/legacy/tests/test_file.py @@ -1,9 +1,7 @@ import pytest import weave -from weave.legacy import context_state, environment, ops - -from ... import api, errors +from weave.legacy.weave import api, context_state, environment, errors, ops def test_dir(): diff --git a/weave/tests/legacy/test_filesystem.py b/weave/legacy/tests/test_filesystem.py similarity index 95% rename from weave/tests/legacy/test_filesystem.py rename to weave/legacy/tests/test_filesystem.py index 3fd8cde70cb..ab8599d9505 100644 --- a/weave/tests/legacy/test_filesystem.py +++ b/weave/legacy/tests/test_filesystem.py @@ -2,7 +2,7 @@ import pytest -from weave.legacy import environment, errors, filesystem +from weave.legacy.weave import environment, errors, filesystem @pytest.fixture() diff --git a/weave/tests/legacy/test_gql_to_weave.py b/weave/legacy/tests/test_gql_to_weave.py similarity index 99% rename from weave/tests/legacy/test_gql_to_weave.py rename to weave/legacy/tests/test_gql_to_weave.py index e3dc869d9bf..8046b0bb81d 100644 --- a/weave/tests/legacy/test_gql_to_weave.py +++ b/weave/legacy/tests/test_gql_to_weave.py @@ -1,5 +1,5 @@ -from weave.legacy import gql_to_weave -from weave.legacy import weave_types as types +from weave.legacy.weave import gql_to_weave +from weave.legacy.weave import weave_types as types def test_simple_query(): diff --git a/weave/tests/legacy/test_graph.py b/weave/legacy/tests/test_graph.py similarity index 96% rename from weave/tests/legacy/test_graph.py rename to weave/legacy/tests/test_graph.py index 70051c60a17..3f1c7ce886f 100644 --- a/weave/tests/legacy/test_graph.py +++ b/weave/legacy/tests/test_graph.py @@ -1,8 +1,6 @@ import weave -from weave.legacy import graph -from weave.legacy import weave_types as types - -from ... import weave_internal +from weave.legacy.weave import graph, weave_internal +from weave.legacy.weave import weave_types as types def test_map_dag_produces_same_len(): diff --git a/weave/tests/legacy/test_graph_debug.py b/weave/legacy/tests/test_graph_debug.py similarity index 96% rename from weave/tests/legacy/test_graph_debug.py rename to weave/legacy/tests/test_graph_debug.py index 957e58699f9..ebe81d6c295 100644 --- a/weave/tests/legacy/test_graph_debug.py +++ b/weave/legacy/tests/test_graph_debug.py @@ -1,6 +1,6 @@ import typing -from weave.legacy import decorator_op, graph, graph_debug +from weave.legacy.weave import decorator_op, graph, graph_debug class RowType(typing.TypedDict): diff --git a/weave/tests/legacy/test_helpers.py b/weave/legacy/tests/test_helpers.py similarity index 100% rename from weave/tests/legacy/test_helpers.py rename to weave/legacy/tests/test_helpers.py diff --git a/weave/tests/legacy/test_hypothesis.py b/weave/legacy/tests/test_hypothesis.py similarity index 98% rename from weave/tests/legacy/test_hypothesis.py rename to weave/legacy/tests/test_hypothesis.py index 915711acfac..90ed51b1626 100644 --- a/weave/tests/legacy/test_hypothesis.py +++ b/weave/legacy/tests/test_hypothesis.py @@ -44,9 +44,9 @@ from hypothesis.strategies import composite import weave -from weave.legacy import artifact_local, ops_arrow, ops_primitives, storage -from weave.legacy.arrow import convert -from weave.legacy.language_features.tagging import tag_store +from weave.legacy.weave import artifact_local, ops_arrow, ops_primitives, storage +from weave.legacy.weave.arrow import convert +from weave.legacy.weave.language_features.tagging import tag_store # Jack this up to find more bugs. EXAMPLES_PER_TEST = 100 diff --git a/weave/tests/legacy/test_index.py b/weave/legacy/tests/test_index.py similarity index 100% rename from weave/tests/legacy/test_index.py rename to weave/legacy/tests/test_index.py diff --git a/weave/tests/legacy/test_infer_types.py b/weave/legacy/tests/test_infer_types.py similarity index 87% rename from weave/tests/legacy/test_infer_types.py rename to weave/legacy/tests/test_infer_types.py index 137af307186..3d08b266378 100644 --- a/weave/tests/legacy/test_infer_types.py +++ b/weave/legacy/tests/test_infer_types.py @@ -1,6 +1,6 @@ import typing -from weave.legacy import graph, infer_types, weave_types +from weave.legacy.weave import graph, infer_types, weave_types def test_node_with_generic(): diff --git a/weave/tests/legacy/test_io_service.py b/weave/legacy/tests/test_io_service.py similarity index 96% rename from weave/tests/legacy/test_io_service.py rename to weave/legacy/tests/test_io_service.py index 053ee5d19cd..52d29e22a2c 100644 --- a/weave/tests/legacy/test_io_service.py +++ b/weave/legacy/tests/test_io_service.py @@ -2,7 +2,7 @@ import pytest -from weave.legacy import filesystem, io_service +from weave.legacy.weave import filesystem, io_service @pytest.mark.timeout(10) diff --git a/weave/tests/legacy/test_join.py b/weave/legacy/tests/test_join.py similarity index 90% rename from weave/tests/legacy/test_join.py rename to weave/legacy/tests/test_join.py index ba6dd4bf70f..f3c06cc956b 100644 --- a/weave/tests/legacy/test_join.py +++ b/weave/legacy/tests/test_join.py @@ -1,6 +1,6 @@ import weave -from weave.legacy import ops_arrow -from weave.legacy.wandb_interface.wandb_stream_table import StreamTable +from weave.legacy.weave import ops_arrow +from weave.legacy.weave.wandb_interface.wandb_stream_table import StreamTable def make_stream_table(*args, **kwargs): @@ -24,7 +24,7 @@ def test_join_awls_with_stitch(user_by_api_key_in_env): feedback_st.log(row) feedback_st.finish() - joined = weave.legacy.ops.join_2( + joined = weave.legacy.weave.ops.join_2( dataset_st.rows(), feedback_st.rows(), lambda row: row["id"], diff --git a/weave/tests/legacy/test_js_compat.py b/weave/legacy/tests/test_js_compat.py similarity index 99% rename from weave/tests/legacy/test_js_compat.py rename to weave/legacy/tests/test_js_compat.py index ba37ee1f65e..9dfa277e439 100644 --- a/weave/tests/legacy/test_js_compat.py +++ b/weave/legacy/tests/test_js_compat.py @@ -7,9 +7,9 @@ # Note: This file is not yet complete, there are existing fixups in the # weave Python code that I haven't documented here. -from weave.legacy import partial_object, weavejs_fixes -from weave.legacy import weave_types as types -from weave.legacy.ops_domain import wb_domain_types +from weave.legacy.weave import partial_object, weavejs_fixes +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.ops_domain import wb_domain_types def test_const_serialization(): diff --git a/weave/tests/legacy/test_language.py b/weave/legacy/tests/test_language.py similarity index 96% rename from weave/tests/legacy/test_language.py rename to weave/legacy/tests/test_language.py index 48277d2fcae..7c225403e2b 100644 --- a/weave/tests/legacy/test_language.py +++ b/weave/legacy/tests/test_language.py @@ -3,8 +3,8 @@ import typing import weave -from weave.legacy import box, context_state -from weave.legacy.language_features.tagging import ( +from weave.legacy.weave import box, context_state +from weave.legacy.weave.language_features.tagging import ( make_tag_getter_op, tag_store, tagged_value_type, diff --git a/weave/tests/legacy/test_language_autocall.py b/weave/legacy/tests/test_language_autocall.py similarity index 100% rename from weave/tests/legacy/test_language_autocall.py rename to weave/legacy/tests/test_language_autocall.py diff --git a/weave/tests/legacy/test_levenshtein.py b/weave/legacy/tests/test_levenshtein.py similarity index 88% rename from weave/tests/legacy/test_levenshtein.py rename to weave/legacy/tests/test_levenshtein.py index 8807cb7dcea..d39d74d8d1e 100644 --- a/weave/tests/legacy/test_levenshtein.py +++ b/weave/legacy/tests/test_levenshtein.py @@ -1,6 +1,6 @@ import pytest -from weave.legacy.ops_primitives.string import _levenshtein +from weave.legacy.weave.ops_primitives.string import _levenshtein @pytest.mark.parametrize( diff --git a/weave/tests/legacy/test_list_arrow_compat.py b/weave/legacy/tests/test_list_arrow_compat.py similarity index 97% rename from weave/tests/legacy/test_list_arrow_compat.py rename to weave/legacy/tests/test_list_arrow_compat.py index a38a409ae23..401a1ae7a5c 100644 --- a/weave/tests/legacy/test_list_arrow_compat.py +++ b/weave/legacy/tests/test_list_arrow_compat.py @@ -3,20 +3,19 @@ import numpy as np import pytest -from weave import weave_internal -from weave.legacy import box, ops -from weave.legacy import ops_arrow as arrow -from weave.legacy import weave_types as types -from weave.legacy.language_features.tagging import ( +from weave.legacy.weave import api as weave +from weave.legacy.weave import box, ops, weave_internal +from weave.legacy.weave import ops_arrow as arrow +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.language_features.tagging import ( make_tag_getter_op, tag_store, tagged_value_type, ) -from weave.legacy.ops_primitives import dict_, list_ +from weave.legacy.weave.ops_primitives import dict_, list_ -from ... import api as weave -from ...tests import tag_test_util as ttu -from .. import list_arrow_test_helpers as lath +from weave.legacy.tests.util import tag_test_util as ttu +from weave.legacy.tests.util import list_arrow_test_helpers as lath def filter_fn(row) -> bool: @@ -637,7 +636,7 @@ def test_tag_pushdown_on_list_of_lists(use_arrow): if use_arrow: list_node = arrow.ops.list_to_arrow(list_node) - from weave.legacy import context_state + from weave.legacy.weave import context_state _loading_builtins_token = context_state.set_loading_built_ins() diff --git a/weave/tests/legacy/test_list_indexing.py b/weave/legacy/tests/test_list_indexing.py similarity index 97% rename from weave/tests/legacy/test_list_indexing.py rename to weave/legacy/tests/test_list_indexing.py index 95290dd3a11..5d76d397b8f 100644 --- a/weave/tests/legacy/test_list_indexing.py +++ b/weave/legacy/tests/test_list_indexing.py @@ -1,7 +1,7 @@ import pytest import weave -from weave.legacy import ops_arrow +from weave.legacy.weave import ops_arrow LIST_CONSTRUCTORS = [ lambda x: x, diff --git a/weave/tests/legacy/test_logging.py b/weave/legacy/tests/test_logging.py similarity index 97% rename from weave/tests/legacy/test_logging.py rename to weave/legacy/tests/test_logging.py index 48463f5e49f..0da877d2cd5 100644 --- a/weave/tests/legacy/test_logging.py +++ b/weave/legacy/tests/test_logging.py @@ -4,9 +4,8 @@ import pytest import requests -from weave.legacy import context, logs, ops, server - -from ... import api, weave_server +from weave import weave_server +from weave.legacy.weave import api, context, logs, ops, server def test_logfile_created(fresh_server_logfile): diff --git a/weave/tests/legacy/test_mappability.py b/weave/legacy/tests/test_mappability.py similarity index 84% rename from weave/tests/legacy/test_mappability.py rename to weave/legacy/tests/test_mappability.py index e7bbcfb79ca..475bebb4490 100644 --- a/weave/tests/legacy/test_mappability.py +++ b/weave/legacy/tests/test_mappability.py @@ -1,10 +1,10 @@ import weave -from weave.legacy import context_state as _context -from weave.legacy import graph -from weave.legacy import weave_types as types +from weave.legacy.weave import context_state as _context +from weave.legacy.weave import graph +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.weave_internal import make_const_node -from ...legacy import registry_mem -from ...weave_internal import make_const_node +from ...legacy.weave import registry_mem _loading_builtins_token = _context.set_loading_built_ins() @@ -61,10 +61,10 @@ def test_non_mapped_use(): def test_non_mapped_serialized(): - node = weave.weave_internal.make_output_node( + node = weave.legacy.weave.weave_internal.make_output_node( weave.types.Int(), _test_add_one.name, - {"x": weave.legacy.graph.ConstNode(weave.types.Int(), 1)}, + {"x": weave.legacy.weave.graph.ConstNode(weave.types.Int(), 1)}, ) assert weave.use(node) == 2 @@ -82,11 +82,11 @@ def test_mapped_nullable_use(): def test_mapped_serialized(): - node = weave.weave_internal.make_output_node( + node = weave.legacy.weave.weave_internal.make_output_node( weave.types.Int(), _test_add_one.name, { - "x": weave.legacy.graph.ConstNode( + "x": weave.legacy.weave.graph.ConstNode( weave.types.List(weave.types.Int()), [1, 2, 3] ) }, @@ -101,10 +101,10 @@ def test_mapped_empty_use(): def test_mapped_empty_serialized(): - node = weave.weave_internal.make_output_node( + node = weave.legacy.weave.weave_internal.make_output_node( weave.types.Int(), _test_add_one.name, - {"x": weave.legacy.graph.ConstNode(weave.types.List(weave.types.Int()), [])}, + {"x": weave.legacy.weave.graph.ConstNode(weave.types.List(weave.types.Int()), [])}, ) assert weave.use(node) == [] @@ -123,7 +123,7 @@ def test_fn(self, a: int) -> int: node = TestType().test_fn(1) assert weave.use(node) == 2 - node_list = weave.legacy.ops.make_list(**{"0": TestType(), "1": TestType()}) + node_list = weave.legacy.weave.ops.make_list(**{"0": TestType(), "1": TestType()}) node = node_list.test_fn(1) assert weave.use(node) == [2, 2] diff --git a/weave/tests/legacy/test_mappers_arrow.py b/weave/legacy/tests/test_mappers_arrow.py similarity index 96% rename from weave/tests/legacy/test_mappers_arrow.py rename to weave/legacy/tests/test_mappers_arrow.py index ff380abdf19..18db29708b1 100644 --- a/weave/tests/legacy/test_mappers_arrow.py +++ b/weave/legacy/tests/test_mappers_arrow.py @@ -2,8 +2,8 @@ import pyarrow as pa -from weave.legacy import mappers_arrow -from weave.legacy import weave_types as types +from weave.legacy.weave import mappers_arrow +from weave.legacy.weave import weave_types as types def test_map_list(): diff --git a/weave/tests/legacy/test_mappers_python.py b/weave/legacy/tests/test_mappers_python.py similarity index 93% rename from weave/tests/legacy/test_mappers_python.py rename to weave/legacy/tests/test_mappers_python.py index 192fdefb041..3b98d6fd5be 100644 --- a/weave/tests/legacy/test_mappers_python.py +++ b/weave/legacy/tests/test_mappers_python.py @@ -1,8 +1,7 @@ import math -from weave import weave_internal -from weave.legacy import api, context, mappers_python, val_const -from weave.legacy import weave_types as types +from weave.legacy.weave import api, context, mappers_python, val_const, weave_internal +from weave.legacy.weave import weave_types as types def test_map_typed_dict(): diff --git a/weave/tests/legacy/test_media.py b/weave/legacy/tests/test_media.py similarity index 98% rename from weave/tests/legacy/test_media.py rename to weave/legacy/tests/test_media.py index 175f4ad38cf..e9d3a6708a9 100644 --- a/weave/tests/legacy/test_media.py +++ b/weave/legacy/tests/test_media.py @@ -3,9 +3,8 @@ import numpy as np import pytest -from weave.legacy import weave_types as types - -from ... import storage +from weave.legacy.weave import storage +from weave.legacy.weave import weave_types as types def test_nparray(): diff --git a/weave/tests/legacy/test_media_user.py b/weave/legacy/tests/test_media_user.py similarity index 84% rename from weave/tests/legacy/test_media_user.py rename to weave/legacy/tests/test_media_user.py index edbceb07217..c7cf985fdd3 100644 --- a/weave/tests/legacy/test_media_user.py +++ b/weave/legacy/tests/test_media_user.py @@ -1,9 +1,8 @@ from PIL import Image -from weave.legacy import context_state -from weave.legacy.ops_primitives import geom as media_user - -from ... import api as weave +from weave.legacy.weave import api as weave +from weave.legacy.weave import context_state +from weave.legacy.weave.ops_primitives import geom as media_user def test_im_with_metadata(): diff --git a/weave/tests/legacy/test_monitoring.py b/weave/legacy/tests/test_monitoring.py similarity index 98% rename from weave/tests/legacy/test_monitoring.py rename to weave/legacy/tests/test_monitoring.py index 73fd0170fd8..d86a32260fe 100644 --- a/weave/tests/legacy/test_monitoring.py +++ b/weave/legacy/tests/test_monitoring.py @@ -5,7 +5,7 @@ import pytest import weave -from weave.legacy.monitoring import monitor +from weave.legacy.weave.monitoring import monitor ### Skipping some tests here. They are for features that no longer exist. Since we're # iterating on the API, I'm not removing them yet. diff --git a/weave/tests/legacy/test_monitoring_openai.py b/weave/legacy/tests/test_monitoring_openai.py similarity index 95% rename from weave/tests/legacy/test_monitoring_openai.py rename to weave/legacy/tests/test_monitoring_openai.py index cc7a1b71d20..01df0bfe8a3 100644 --- a/weave/tests/legacy/test_monitoring_openai.py +++ b/weave/legacy/tests/test_monitoring_openai.py @@ -12,13 +12,13 @@ from openai.types.completion_usage import CompletionUsage import weave -from weave.legacy.monitoring import init_monitor -from weave.legacy.monitoring.openai import util -from weave.legacy.monitoring.openai.models import * -from weave.legacy.monitoring.openai.models import Context -from weave.legacy.monitoring.openai.openai import patch, unpatch -from weave.legacy.monitoring.openai.util import Context -from weave.legacy.wandb_interface.wandb_stream_table import StreamTable +from weave.legacy.weave.monitoring import init_monitor +from weave.legacy.weave.monitoring.openai import util +from weave.legacy.weave.monitoring.openai.models import * +from weave.legacy.weave.monitoring.openai.models import Context +from weave.legacy.weave.monitoring.openai.openai import patch, unpatch +from weave.legacy.weave.monitoring.openai.util import Context +from weave.legacy.weave.wandb_interface.wandb_stream_table import StreamTable @pytest.fixture @@ -453,7 +453,7 @@ def test_log_to_span_basic( streamtable = make_stream_table( stream_name, project_name=project, entity_name=entity ) - chat_completions = weave.legacy.monitoring.openai.openai.ChatCompletions( + chat_completions = weave.legacy.weave.monitoring.openai.openai.ChatCompletions( mocked_create ) create_input = dict( @@ -481,7 +481,7 @@ def test_log_to_span_streaming( reassembled_chat_completion_message, client, ): - chat_completions = weave.legacy.monitoring.openai.openai.ChatCompletions( + chat_completions = weave.legacy.weave.monitoring.openai.openai.ChatCompletions( mocked_streaming_create ) create_input = dict( @@ -512,7 +512,7 @@ async def test_log_to_span_async_streaming( reassembled_chat_completion_message, client, ): - chat_completions = weave.legacy.monitoring.openai.openai.AsyncChatCompletions( + chat_completions = weave.legacy.weave.monitoring.openai.openai.AsyncChatCompletions( mocked_async_streaming_create ) create_input = dict( diff --git a/weave/tests/legacy/test_mutation2.py b/weave/legacy/tests/test_mutation2.py similarity index 61% rename from weave/tests/legacy/test_mutation2.py rename to weave/legacy/tests/test_mutation2.py index d344f10b0f9..d9e28fadfb7 100644 --- a/weave/tests/legacy/test_mutation2.py +++ b/weave/legacy/tests/test_mutation2.py @@ -1,24 +1,22 @@ import weave -from weave.legacy import ref_base, uris - -from ... import weave_internal +from weave.legacy.weave import ref_base, uris, weave_internal def test_mutation_set_direct_call(): - val = weave.legacy.ops.TypedDict.pick({"a": {"b": 5}}, "a")["b"] - set_result = weave.legacy.ops.set(val, 9) + val = weave.legacy.weave.ops.TypedDict.pick({"a": {"b": 5}}, "a")["b"] + set_result = weave.legacy.weave.ops.set(val, 9) assert set_result == {"a": {"b": 9}} def test_mutation_set_dispatch(): - val = weave.legacy.ops.TypedDict.pick({"a": {"b": 5}}, "a")["b"] + val = weave.legacy.weave.ops.TypedDict.pick({"a": {"b": 5}}, "a")["b"] set_result = val.set(9) assert set_result == {"a": {"b": 9}} def test_mutation_artifact(): weave.save([1, 2, 3], "art:main") - art = weave.legacy.ops.get("local-artifact:///art:main/obj") + art = weave.legacy.weave.ops.get("local-artifact:///art:main/obj") art.append(4) new_art = weave.storage.get("local-artifact:///art:main/obj") assert new_art == [1, 2, 3, 4] @@ -27,12 +25,12 @@ def test_mutation_artifact(): def test_mutation_lazy(): # This is how weavejs does it. weave.save([1, 2, 3], "art:main") - art = weave.legacy.ops.get("local-artifact:///art:main/obj") + art = weave.legacy.weave.ops.get("local-artifact:///art:main/obj") # Quote lhs expr, so that it is not evaluated. quoted_art = weave_internal.const(art) - expr = weave.legacy.ops.append.lazy_call(quoted_art, 4, {}) + expr = weave.legacy.weave.ops.append.lazy_call(quoted_art, 4, {}) weave.use(expr) new_art = weave.storage.get("local-artifact:///art:main/obj") @@ -42,9 +40,9 @@ def test_mutation_lazy(): def test_mutation_lazy_works_without_quoting(): # This relies on "auto quoting behavior". See compile_quote weave.save([1, 2, 3], "art:main") - art = weave.legacy.ops.get("local-artifact:///art:main/obj") + art = weave.legacy.weave.ops.get("local-artifact:///art:main/obj") - expr = weave.legacy.ops.append.lazy_call(art, 4, {}) + expr = weave.legacy.weave.ops.append.lazy_call(art, 4, {}) weave.use(expr) new_art = weave.storage.get("local-artifact:///art:main/obj") @@ -53,14 +51,14 @@ def test_mutation_lazy_works_without_quoting(): def test_merge(): weave.save({"a": 5, "b": 6}, "my-dict:latest") - dict_obj = weave.legacy.ops.get("local-artifact:///my-dict:latest/obj") - weave.legacy.ops.set(dict_obj["a"], 17, root_args={"branch": "my-branch"}) - modified_dict_obj = weave.legacy.ops.get("local-artifact:///my-dict:my-branch/obj") - new_uri = weave.legacy.ops.merge_artifact(modified_dict_obj) - dict_obj_node = weave.legacy.ops.get(new_uri) + dict_obj = weave.legacy.weave.ops.get("local-artifact:///my-dict:latest/obj") + weave.legacy.weave.ops.set(dict_obj["a"], 17, root_args={"branch": "my-branch"}) + modified_dict_obj = weave.legacy.weave.ops.get("local-artifact:///my-dict:my-branch/obj") + new_uri = weave.legacy.weave.ops.merge_artifact(modified_dict_obj) + dict_obj_node = weave.legacy.weave.ops.get(new_uri) assert ( weave.use(dict_obj_node) - == weave.use(weave.legacy.ops.get("local-artifact:///my-dict:latest/obj")) + == weave.use(weave.legacy.weave.ops.get("local-artifact:///my-dict:latest/obj")) == {"a": 17, "b": 6} ) @@ -70,24 +68,24 @@ def test_merge_no_version(): uri = get_node.from_op.inputs["uri"].val # type: ignore # uri now has a direct commit hash for the version - dict_obj = weave.legacy.ops.get(uri) - weave.legacy.ops.set(dict_obj["a"], 17, root_args={"branch": "my-branch"}) - modified_dict_obj = weave.legacy.ops.get("local-artifact:///my-dict:my-branch/obj") - new_uri = weave.legacy.ops.merge_artifact(modified_dict_obj) - dict_obj_node = weave.legacy.ops.get(new_uri) + dict_obj = weave.legacy.weave.ops.get(uri) + weave.legacy.weave.ops.set(dict_obj["a"], 17, root_args={"branch": "my-branch"}) + modified_dict_obj = weave.legacy.weave.ops.get("local-artifact:///my-dict:my-branch/obj") + new_uri = weave.legacy.weave.ops.merge_artifact(modified_dict_obj) + dict_obj_node = weave.legacy.weave.ops.get(new_uri) assert weave.use(dict_obj_node) == {"a": 17, "b": 6} def test_merge_list_type(): - from weave.legacy import object_context + from weave.legacy.weave import object_context weave.save([], "my-list:latest") - obj = weave.legacy.ops.get("local-artifact:///my-list:latest/obj") + obj = weave.legacy.weave.ops.get("local-artifact:///my-list:latest/obj") with object_context.object_context(): obj.append({"a": "x"}, {}) obj.append([1], {}) - assert weave.use(weave.legacy.ops.get("local-artifact:///my-list:latest/obj")) == [ + assert weave.use(weave.legacy.weave.ops.get("local-artifact:///my-list:latest/obj")) == [ {"a": "x"}, [1], ] @@ -97,7 +95,7 @@ def test_artifact_history_local(): num_versions = 4 uri = "local-artifact:///art:main/obj" weave.save([0], "art:main") - art = weave.legacy.ops.get(uri) + art = weave.legacy.weave.ops.get(uri) for i in range(num_versions): art.append(i + 1) @@ -107,7 +105,7 @@ def test_artifact_history_local(): assert new_art == total_list for i in range(num_versions): - new_uri = weave.legacy.ops.undo_artifact(weave.legacy.ops.get(uri)) + new_uri = weave.legacy.weave.ops.undo_artifact(weave.legacy.weave.ops.get(uri)) # We expect these to be the same since the branch pointer changed assert new_uri == uri res = weave.storage.get(uri) @@ -118,12 +116,12 @@ def test_artifact_history_local_from_hash(): num_versions = 4 uri = "local-artifact:///art:main/obj" weave.save([0], "art:main") - art = weave.legacy.ops.get(uri) + art = weave.legacy.weave.ops.get(uri) for i in range(num_versions): art.append(i + 1) - hash_uri = weave.legacy.uris.WeaveURI.parse(uri).to_ref().artifact.uri + hash_uri = weave.legacy.weave.uris.WeaveURI.parse(uri).to_ref().artifact.uri assert "main" not in hash_uri total_list = list(range(num_versions + 1)) @@ -132,7 +130,7 @@ def test_artifact_history_local_from_hash(): new_uri = hash_uri for i in range(num_versions): - new_uri = weave.legacy.ops.undo_artifact(weave.legacy.ops.get(new_uri)) + new_uri = weave.legacy.weave.ops.undo_artifact(weave.legacy.weave.ops.get(new_uri)) assert "main" not in new_uri res = weave.storage.get(new_uri + "/obj") assert res == total_list[: num_versions - i] @@ -142,16 +140,16 @@ def test_artifact_history_remote_with_branch(user_by_api_key_in_env): num_versions = 2 uri = "local-artifact:///art:main/obj" weave.save([0], "art:main") - art = weave.legacy.ops.get(uri) - published_art_uri = weave.legacy.ops.publish_artifact(art, "art", None, None) + art = weave.legacy.weave.ops.get(uri) + published_art_uri = weave.legacy.weave.ops.publish_artifact(art, "art", None, None) - art = weave.legacy.ops.get( + art = weave.legacy.weave.ops.get( f"wandb-artifact:///{user_by_api_key_in_env.username}/weave/art:latest/obj" ) for i in range(num_versions): res_uri = art.append(i + 1) - art = weave.legacy.ops.get(res_uri) + art = weave.legacy.weave.ops.get(res_uri) new_uri = res_uri total_list = list(range(num_versions + 1)) @@ -159,7 +157,7 @@ def test_artifact_history_remote_with_branch(user_by_api_key_in_env): assert new_art == total_list for i in range(num_versions): - new_uri = weave.legacy.ops.undo_artifact(weave.legacy.ops.get(new_uri)) + new_uri = weave.legacy.weave.ops.undo_artifact(weave.legacy.weave.ops.get(new_uri)) res = weave.storage.get(new_uri) if i == num_versions - 1: assert new_uri.startswith("wandb") @@ -172,16 +170,16 @@ def test_artifact_history_remote_with_hash(user_by_api_key_in_env): num_versions = 2 uri = "local-artifact:///art:main/obj" weave.save([0], "art:main") - art = weave.legacy.ops.get(uri) - published_art_uri = weave.legacy.ops.publish_artifact(art, "art", None, None) + art = weave.legacy.weave.ops.get(uri) + published_art_uri = weave.legacy.weave.ops.publish_artifact(art, "art", None, None) assert "latest" not in published_art_uri assert "main" not in published_art_uri - art = weave.legacy.ops.get(published_art_uri) + art = weave.legacy.weave.ops.get(published_art_uri) for i in range(num_versions): res_uri = art.append(i + 1) - art = weave.legacy.ops.get(res_uri) + art = weave.legacy.weave.ops.get(res_uri) new_uri = res_uri total_list = list(range(num_versions + 1)) @@ -189,7 +187,7 @@ def test_artifact_history_remote_with_hash(user_by_api_key_in_env): assert new_art == total_list for i in range(num_versions): - new_uri = weave.legacy.ops.undo_artifact(weave.legacy.ops.get(new_uri)) + new_uri = weave.legacy.weave.ops.undo_artifact(weave.legacy.weave.ops.get(new_uri)) res = weave.storage.get(new_uri) if i == num_versions - 1: assert new_uri.startswith("wandb") diff --git a/weave/tests/legacy/test_mutations.py b/weave/legacy/tests/test_mutations.py similarity index 94% rename from weave/tests/legacy/test_mutations.py rename to weave/legacy/tests/test_mutations.py index 983b777ee05..abdc289cb73 100644 --- a/weave/tests/legacy/test_mutations.py +++ b/weave/legacy/tests/test_mutations.py @@ -1,7 +1,5 @@ -from weave.legacy import ops - -from ... import api as weave -from ... import storage, weave_internal +from weave.legacy.weave import api as weave +from weave.legacy.weave import ops, storage, weave_internal def test_autocommit(cereal_csv): diff --git a/weave/tests/legacy/test_node_ref.py b/weave/legacy/tests/test_node_ref.py similarity index 76% rename from weave/tests/legacy/test_node_ref.py rename to weave/legacy/tests/test_node_ref.py index eaacc2c3190..75216f629ad 100644 --- a/weave/tests/legacy/test_node_ref.py +++ b/weave/legacy/tests/test_node_ref.py @@ -1,7 +1,7 @@ -from weave.legacy import graph +from weave.legacy.weave import api as weave +from weave.legacy.weave import graph -from ... import api as weave -from ...legacy import node_ref +from ...legacy.weave import node_ref def test_node_to_ref(): diff --git a/weave/tests/legacy/test_nullability.py b/weave/legacy/tests/test_nullability.py similarity index 95% rename from weave/tests/legacy/test_nullability.py rename to weave/legacy/tests/test_nullability.py index 41ab3fd24e8..9d07569b799 100644 --- a/weave/tests/legacy/test_nullability.py +++ b/weave/legacy/tests/test_nullability.py @@ -3,8 +3,8 @@ import pytest import weave -from weave.legacy import context_state as _context -from weave.weave_internal import make_const_node, make_output_node +from weave.legacy.weave import context_state as _context +from weave.legacy.weave.weave_internal import make_const_node, make_output_node _loading_builtins_token = _context.set_loading_built_ins() @@ -57,7 +57,7 @@ def test_basic_nullability(): def test_basic_nullability_in_mappability(): b_arr = weave.save([2]) maybe_int_arr = weave.save( - weave.legacy.graph.ConstNode( + weave.legacy.weave.graph.ConstNode( weave.types.List(weave.types.optional(weave.types.Int())), [1, None] ) ) diff --git a/weave/tests/legacy/test_number_bin.py b/weave/legacy/tests/test_number_bin.py similarity index 68% rename from weave/tests/legacy/test_number_bin.py rename to weave/legacy/tests/test_number_bin.py index 22a5a5cd72a..1475ef9c49d 100644 --- a/weave/tests/legacy/test_number_bin.py +++ b/weave/legacy/tests/test_number_bin.py @@ -4,8 +4,8 @@ def test_number_bin_vector(): - awl = weave.legacy.ops.to_weave_arrow([1, 2, 3, 4, 5]) - mapped = awl.map(lambda x: x.bin(weave.legacy.ops.number_bins_fixed(2))) + awl = weave.legacy.weave.ops.to_weave_arrow([1, 2, 3, 4, 5]) + mapped = awl.map(lambda x: x.bin(weave.legacy.weave.ops.number_bins_fixed(2))) res = weave.use(mapped).to_pylist_tagged() assert res == [ {"start": 0.0, "stop": 2.0}, diff --git a/weave/tests/legacy/test_numpy.py b/weave/legacy/tests/test_numpy.py similarity index 96% rename from weave/tests/legacy/test_numpy.py rename to weave/legacy/tests/test_numpy.py index 99275de1e7d..740c281aa9e 100644 --- a/weave/tests/legacy/test_numpy.py +++ b/weave/legacy/tests/test_numpy.py @@ -1,10 +1,10 @@ import numpy as np -from weave.legacy import artifact_fs, artifact_wandb -from weave.legacy import weave_types as types -from weave.legacy.ops_domain import table +from weave.legacy.weave import artifact_fs, artifact_wandb +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.ops_domain import table -from ...legacy import types_numpy as numpy_types +from ...legacy.weave import types_numpy as numpy_types def test_construct_numpy_type(): diff --git a/weave/tests/legacy/test_op.py b/weave/legacy/tests/test_op.py similarity index 96% rename from weave/tests/legacy/test_op.py rename to weave/legacy/tests/test_op.py index 46cea2177cb..8bb75326f50 100644 --- a/weave/tests/legacy/test_op.py +++ b/weave/legacy/tests/test_op.py @@ -2,10 +2,10 @@ import pytest -from weave import types, weave_internal -from weave.legacy import api as weave -from weave.legacy import context_state, graph, storage, uris -from weave.legacy import context_state as _context_state +from weave.legacy.weave import api as weave +from weave.legacy.weave import context_state, graph, storage, uris, weave_internal +from weave.legacy.weave import context_state as _context_state +from weave.legacy.weave import weave_types as types from . import test_helpers diff --git a/weave/tests/legacy/test_op_behaviors.py b/weave/legacy/tests/test_op_behaviors.py similarity index 98% rename from weave/tests/legacy/test_op_behaviors.py rename to weave/legacy/tests/test_op_behaviors.py index e49ff51c9c1..efbc05caf2a 100644 --- a/weave/tests/legacy/test_op_behaviors.py +++ b/weave/legacy/tests/test_op_behaviors.py @@ -12,27 +12,28 @@ import pytest import weave -from weave.legacy import ( +from weave.legacy.weave import ( graph, graph_debug, language_nullability, op_def, ops_arrow, ops_primitives, + storage, + weave_internal, ) -from weave.legacy.language_features.tagging import make_tag_getter_op -from weave.legacy.language_features.tagging.tagged_value_type import ( +from weave.legacy.weave.language_features.tagging import make_tag_getter_op +from weave.legacy.weave.language_features.tagging.tagged_value_type import ( TaggedValueType, ) -from ... import storage, weave_internal -from ...legacy import registry_mem -from ..concrete_tagged_value import ( +from ...legacy.weave import registry_mem +from weave.legacy.tests.util.concrete_tagged_value import ( TaggedValue, concrete_from_tagstore, concrete_to_tagstore, ) -from ..op_specs import OP_TEST_SPECS, OpSpec, OpSpecTestCase +from weave.legacy.tests.util.op_specs import OP_TEST_SPECS, OpSpec, OpSpecTestCase def assert_equal_with_tags(node: graph.Node, v: typing.Any, expected: typing.Any): @@ -82,7 +83,7 @@ def check_case(called: graph.Node, result_type: weave.types.Type, result: typing ), f"Expected op output type: {result_type}, but got {called.type}" # This is a way to save the final output, preserving tags. - result_ref = weave.use(weave.legacy.ops.save_to_ref(called, None)) + result_ref = weave.use(weave.legacy.weave.ops.save_to_ref(called, None)) if result_ref == None: actual_result = None else: diff --git a/weave/tests/legacy/test_op_coverage.py b/weave/legacy/tests/test_op_coverage.py similarity index 99% rename from weave/tests/legacy/test_op_coverage.py rename to weave/legacy/tests/test_op_coverage.py index cede44a1109..f933b3e978d 100644 --- a/weave/tests/legacy/test_op_coverage.py +++ b/weave/legacy/tests/test_op_coverage.py @@ -1,4 +1,4 @@ -from ...legacy import registry_mem +from ...legacy.weave import registry_mem def make_error_message(missing_ops, section_name): diff --git a/weave/tests/legacy/test_op_def.py b/weave/legacy/tests/test_op_def.py similarity index 93% rename from weave/tests/legacy/test_op_def.py rename to weave/legacy/tests/test_op_def.py index aa24f185282..dc8fa42a100 100644 --- a/weave/tests/legacy/test_op_def.py +++ b/weave/legacy/tests/test_op_def.py @@ -5,9 +5,8 @@ import pytest -from weave.legacy import context_state - -from ... import api as weave +from weave.legacy.weave import api as weave +from weave.legacy.weave import context_state _loading_builtins_token = context_state.set_loading_built_ins() @@ -56,8 +55,7 @@ def wrap(obj: typing.Any): context_state.clear_loading_built_ins(_loading_builtins_token) - -test_data = json.load(open("./tests/legacy/test_op_def_data.json")) +test_data = json.load(open("./legacy/tests/test_op_def_data.json")) def test_op_def_to_dict(): diff --git a/weave/tests/legacy/test_op_def_data.json b/weave/legacy/tests/test_op_def_data.json similarity index 100% rename from weave/tests/legacy/test_op_def_data.json rename to weave/legacy/tests/test_op_def_data.json diff --git a/weave/tests/legacy/test_op_def_type.py b/weave/legacy/tests/test_op_def_type.py similarity index 89% rename from weave/tests/legacy/test_op_def_type.py rename to weave/legacy/tests/test_op_def_type.py index 303705fac3b..d6eedd6ab0f 100644 --- a/weave/tests/legacy/test_op_def_type.py +++ b/weave/legacy/tests/test_op_def_type.py @@ -1,6 +1,6 @@ import typing -from weave.legacy import op_def_type +from weave.legacy.weave import op_def_type class MyTypedDict(typing.TypedDict): diff --git a/weave/tests/legacy/test_op_dispatching.py b/weave/legacy/tests/test_op_dispatching.py similarity index 93% rename from weave/tests/legacy/test_op_dispatching.py rename to weave/legacy/tests/test_op_dispatching.py index fbd1d51df36..4d583e5cf86 100644 --- a/weave/tests/legacy/test_op_dispatching.py +++ b/weave/legacy/tests/test_op_dispatching.py @@ -1,13 +1,11 @@ import pytest import weave -from weave.legacy import context_state as _context -from weave.legacy import graph -from weave.legacy.dispatch import _dispatch_first_arg, _resolve_op_ambiguity -from weave.legacy.language_features.tagging.tagged_value_type import TaggedValueType -from weave.legacy.ops_domain import wb_domain_types - -from ... import weave_internal +from weave.legacy.weave import context_state as _context +from weave.legacy.weave import graph, weave_internal +from weave.legacy.weave.dispatch import _dispatch_first_arg, _resolve_op_ambiguity +from weave.legacy.weave.language_features.tagging.tagged_value_type import TaggedValueType +from weave.legacy.weave.ops_domain import wb_domain_types _loading_builtins_token = _context.set_loading_built_ins() @@ -73,7 +71,7 @@ def test_pick_map(): def test_json_pick_map(): - res = weave.legacy.graph.OutputNode.from_json( + res = weave.legacy.weave.graph.OutputNode.from_json( { "nodeType": "output", "type": {"type": "list", "objectType": "number"}, @@ -171,7 +169,7 @@ def test_nested_js_dict_pick(): def test_dispatch_of_ambiguous_ops(): - artifacts_node = weave.legacy.ops.project("a", "b").artifacts() + artifacts_node = weave.legacy.weave.ops.project("a", "b").artifacts() # just making this call is the test since it is # not clear if it should be artifact-project or # tag-project diff --git a/weave/tests/legacy/test_op_serialization.py b/weave/legacy/tests/test_op_serialization.py similarity index 93% rename from weave/tests/legacy/test_op_serialization.py rename to weave/legacy/tests/test_op_serialization.py index 7c08a59b982..c666770a379 100644 --- a/weave/tests/legacy/test_op_serialization.py +++ b/weave/legacy/tests/test_op_serialization.py @@ -1,9 +1,9 @@ import pytest import weave -import weave.legacy +import weave.legacy.weave -ops = weave.legacy.registry_mem.memory_registry.list_ops() +ops = weave.legacy.weave.registry_mem.memory_registry.list_ops() def output_type_dict_is_const_function_node(output_type_dict): diff --git a/weave/tests/legacy/test_panel_coverage.py b/weave/legacy/tests/test_panel_coverage.py similarity index 97% rename from weave/tests/legacy/test_panel_coverage.py rename to weave/legacy/tests/test_panel_coverage.py index b811196a18a..f1518968b5b 100644 --- a/weave/tests/legacy/test_panel_coverage.py +++ b/weave/legacy/tests/test_panel_coverage.py @@ -14,7 +14,7 @@ def inheritors(klass): def all_panels(): - return inheritors(weave.legacy.panel.Panel) + return inheritors(weave.legacy.weave.panel.Panel) def test_panel_coverage(): diff --git a/weave/tests/legacy/test_panel_time_series.py b/weave/legacy/tests/test_panel_time_series.py similarity index 78% rename from weave/tests/legacy/test_panel_time_series.py rename to weave/legacy/tests/test_panel_time_series.py index 169b6bb40aa..6b0faab5cbe 100644 --- a/weave/tests/legacy/test_panel_time_series.py +++ b/weave/legacy/tests/test_panel_time_series.py @@ -3,7 +3,7 @@ import wandb import weave -from weave.legacy.ecosystem.wandb.panel_time_series import TimeSeries +from weave.legacy.weave.ecosystem.wandb.panel_time_series import TimeSeries def test_panel_timeseries(user_by_api_key_in_env): @@ -14,7 +14,7 @@ def test_panel_timeseries(user_by_api_key_in_env): run.finish() history_node = ( - weave.legacy.ops.project(run.entity, run.project).run(run.id).history2() + weave.legacy.weave.ops.project(run.entity, run.project).run(run.id).history2() ) panel = TimeSeries(history_node) init_config_node = panel.initialize() diff --git a/weave/tests/legacy/test_panels.py b/weave/legacy/tests/test_panels.py similarity index 78% rename from weave/tests/legacy/test_panels.py rename to weave/legacy/tests/test_panels.py index 867f9842c0f..1f309042751 100644 --- a/weave/tests/legacy/test_panels.py +++ b/weave/legacy/tests/test_panels.py @@ -1,11 +1,11 @@ import pytest import weave -from weave.legacy.panels import panel_plot +from weave.legacy.weave import storage, weave_internal +from weave.legacy.weave.panels import panel_plot -from ... import storage, weave_internal -from ...legacy.panels.panel_group import Group -from ...legacy.panels.panel_slider import Slider +from ...legacy.weave.panels.panel_group import Group +from ...legacy.weave.panels.panel_slider import Slider def test_panel_id(): @@ -82,7 +82,7 @@ def test_synced(): def test_object_picker_choice_type(): ints = weave.save([1, 2, 3], name="my-ints") - panel = weave.legacy.panels.ObjectPicker(ints) + panel = weave.legacy.weave.panels.ObjectPicker(ints) panel_node = weave_internal.make_var_node(weave.type_of(panel), "panel") choice = panel_node.config.choice assert choice.type == weave.types.Function({}, weave.types.Int()) @@ -92,17 +92,17 @@ def test_facet_selected(): data = weave.save( [{"guess": "dog", "truth": "cat"}, {"guess": "dog", "truth": "dog"}] ) - facet = weave.legacy.panels.Group( + facet = weave.legacy.weave.panels.Group( equalSize=True, items={ - "confusion": weave.legacy.panels.Facet( + "confusion": weave.legacy.weave.panels.Facet( data, x=lambda row: row["guess"], y=lambda row: row["truth"], - select=lambda row: weave.legacy.panels.Group( + select=lambda row: weave.legacy.weave.panels.Group( layered=True, items={ - "color": weave.legacy.panels.Color(row.count() / 50), + "color": weave.legacy.weave.panels.Color(row.count() / 50), "count": row.count(), }, ), @@ -115,11 +115,11 @@ def test_facet_selected(): def test_board(): # Just make sure it runs for now. - weave.legacy.panels.Board( + weave.legacy.weave.panels.Board( { - "nums": weave.legacy.ops.range(0, 3, 1), + "nums": weave.legacy.weave.ops.range(0, 3, 1), }, - [weave.legacy.panels.BoardPanel(id="panel0", panel=lambda nums: nums)], + [weave.legacy.weave.panels.BoardPanel(id="panel0", panel=lambda nums: nums)], ) @@ -130,6 +130,6 @@ def test_plot_constants_assign(): def test_plot_assign(): - assert weave.legacy.panels.Plot.WeaveType().assign_type( - weave.type_of(weave.legacy.panels.Plot([{"a": 5}])) + assert weave.legacy.weave.panels.Plot.WeaveType().assign_type( + weave.type_of(weave.legacy.weave.panels.Plot([{"a": 5}])) ) diff --git a/weave/tests/legacy/test_partial_object.py b/weave/legacy/tests/test_partial_object.py similarity index 98% rename from weave/tests/legacy/test_partial_object.py rename to weave/legacy/tests/test_partial_object.py index 8096e6061b5..6a1955f23fe 100644 --- a/weave/tests/legacy/test_partial_object.py +++ b/weave/legacy/tests/test_partial_object.py @@ -1,11 +1,11 @@ -from weave.legacy import compile, ops -from weave.legacy import weave_types as types -from weave.legacy.language_features.tagging.tagged_value_type import TaggedValueType -from weave.legacy.ops_domain import wb_domain_types as wdt -from weave.legacy.ops_domain.project_ops import root_all_projects -from weave.legacy.ops_domain.report_ops import root_all_reports - -from ... import api as weave +from weave.legacy.weave import api as weave +from weave.legacy.weave import compile, ops +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.language_features.tagging.tagged_value_type import TaggedValueType +from weave.legacy.weave.ops_domain import wb_domain_types as wdt +from weave.legacy.weave.ops_domain.project_ops import root_all_projects +from weave.legacy.weave.ops_domain.report_ops import root_all_reports + from .test_wb import table_mock1_no_display_name diff --git a/weave/tests/legacy/test_plot.py b/weave/legacy/tests/test_plot.py similarity index 98% rename from weave/tests/legacy/test_plot.py rename to weave/legacy/tests/test_plot.py index 7f0658d2529..93ce632dd1e 100644 --- a/weave/tests/legacy/test_plot.py +++ b/weave/legacy/tests/test_plot.py @@ -4,11 +4,10 @@ import pytest import weave -from weave.legacy import graph -from weave.legacy import weave_types as types -from weave.legacy.panels.panel_plot import Plot, PlotConstants, Series +from weave.legacy.weave import graph, storage +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.panels.panel_plot import Plot, PlotConstants, Series -from ... import storage from .test_run_segment import create_experiment @@ -39,11 +38,11 @@ def test_multi_series_plot_config_with_grouping(): weave.save(last_segment) plot = Plot(last_segment.experiment()) plot.set_x( - lambda row: weave.legacy.ops.number_bin( - row["step"], weave.legacy.ops.numbers_bins_equal([1, 2000], 2) + lambda row: weave.legacy.weave.ops.number_bin( + row["step"], weave.legacy.weave.ops.numbers_bins_equal([1, 2000], 2) ) ) - plot.set_y(lambda row: weave.legacy.ops.numbers_avg(row["metric0"])) + plot.set_y(lambda row: weave.legacy.weave.ops.numbers_avg(row["metric0"])) plot.groupby_x() plot.set_mark_constant("line") @@ -51,8 +50,8 @@ def test_multi_series_plot_config_with_grouping(): series2 = plot.config.series[0].clone() plot.add_series(series2) - series2.set_y(lambda row: weave.legacy.ops.numbers_min(row["metric0"])) - series2.set_y2(lambda row: weave.legacy.ops.numbers_max(row["metric0"])) + series2.set_y(lambda row: weave.legacy.weave.ops.numbers_min(row["metric0"])) + series2.set_y2(lambda row: weave.legacy.weave.ops.numbers_max(row["metric0"])) series2.set_mark_constant("area") plot.groupby_x() @@ -70,20 +69,20 @@ def test_multi_series_grouping(): weave.save(last_segment) plot = Plot(last_segment.experiment()) plot.set_x( - lambda row: weave.legacy.ops.number_bin( - row["step"], weave.legacy.ops.numbers_bins_equal([1, 2000], 2) + lambda row: weave.legacy.weave.ops.number_bin( + row["step"], weave.legacy.weave.ops.numbers_bins_equal([1, 2000], 2) ) ) plot.groupby_x() - plot.set_y(lambda row: weave.legacy.ops.numbers_avg(row["metric0"])) + plot.set_y(lambda row: weave.legacy.weave.ops.numbers_avg(row["metric0"])) plot.set_mark_constant("line") series2 = plot.config.series[0].clone() plot.add_series(series2) - series2.set_y(lambda row: weave.legacy.ops.numbers_min(row["metric0"])) - series2.set_y2(lambda row: weave.legacy.ops.numbers_max(row["metric0"])) + series2.set_y(lambda row: weave.legacy.weave.ops.numbers_min(row["metric0"])) + series2.set_y2(lambda row: weave.legacy.weave.ops.numbers_max(row["metric0"])) series2.set_mark_constant("area") plot2 = copy.deepcopy(plot) @@ -101,8 +100,8 @@ def test_multi_series_setting(): weave.save(last_segment) plot = Plot(last_segment.experiment()) plot.set_x( - lambda row: weave.legacy.ops.number_bin( - row["step"], weave.legacy.ops.numbers_bins_equal([1, 2000], 2) + lambda row: weave.legacy.weave.ops.number_bin( + row["step"], weave.legacy.weave.ops.numbers_bins_equal([1, 2000], 2) ) ) @@ -157,18 +156,18 @@ def test_actual_config_value(fixed_random_seed): weave.save(last_segment) plot = Plot(last_segment.experiment()) plot.set_x( - lambda row: weave.legacy.ops.number_bin( - row["step"], weave.legacy.ops.numbers_bins_equal([1, 2000], 2) + lambda row: weave.legacy.weave.ops.number_bin( + row["step"], weave.legacy.weave.ops.numbers_bins_equal([1, 2000], 2) ) ) - plot.set_y(lambda row: weave.legacy.ops.numbers_avg(row["metric0"])) + plot.set_y(lambda row: weave.legacy.weave.ops.numbers_avg(row["metric0"])) plot.set_mark_constant("line") series2 = plot.config.series[0].clone() plot.add_series(series2) - series2.set_y(lambda row: weave.legacy.ops.numbers_min(row["metric0"])) - series2.set_y2(lambda row: weave.legacy.ops.numbers_max(row["metric0"])) + series2.set_y(lambda row: weave.legacy.weave.ops.numbers_min(row["metric0"])) + series2.set_y2(lambda row: weave.legacy.weave.ops.numbers_max(row["metric0"])) series2.set_mark_constant("area") assert storage.to_python(plot.config) == { "_type": { @@ -1767,7 +1766,7 @@ def test_actual_config_value(fixed_random_seed): def test_panel_plot_scale_serialization(): # checks a problem case where scale would not be serialized correctly as an AxisScale object - plot = weave.legacy.panels.Plot( + plot = weave.legacy.weave.panels.Plot( [1, 2, 3, 4], x=lambda row: row, x_title="x", diff --git a/weave/tests/legacy/test_projection_timeout.py b/weave/legacy/tests/test_projection_timeout.py similarity index 90% rename from weave/tests/legacy/test_projection_timeout.py rename to weave/legacy/tests/test_projection_timeout.py index b9fd2a7e21e..302b2247226 100644 --- a/weave/tests/legacy/test_projection_timeout.py +++ b/weave/legacy/tests/test_projection_timeout.py @@ -1,7 +1,7 @@ import numpy as np import pytest -from weave.legacy.ops_primitives import projection_utils +from weave.legacy.weave.ops_primitives import projection_utils from ... import errors diff --git a/weave/tests/legacy/test_publish_flow.py b/weave/legacy/tests/test_publish_flow.py similarity index 90% rename from weave/tests/legacy/test_publish_flow.py rename to weave/legacy/tests/test_publish_flow.py index 228fdd200d0..c7fe8c38172 100644 --- a/weave/tests/legacy/test_publish_flow.py +++ b/weave/legacy/tests/test_publish_flow.py @@ -3,20 +3,20 @@ import pytest import weave -from weave.legacy import panels, storage -from weave.legacy.artifact_fs import BranchPointType, FilesystemArtifactRef -from weave.legacy.artifact_local import ( +from weave.legacy.weave import panels, storage +from weave.legacy.weave.artifact_fs import BranchPointType, FilesystemArtifactRef +from weave.legacy.weave.artifact_local import ( LocalArtifact, LocalArtifactRef, WeaveLocalArtifactURI, ) -from weave.legacy.artifact_wandb import ( +from weave.legacy.weave.artifact_wandb import ( WandbArtifact, WandbArtifactRef, WeaveWBArtifactURI, likely_commit_hash, ) -from weave.legacy.uris import WeaveURI +from weave.legacy.weave.uris import WeaveURI def test_publish_values(user_by_api_key_in_env): @@ -26,7 +26,7 @@ def test_publish_values(user_by_api_key_in_env): def test_publish_panel(user_by_api_key_in_env): - from weave.legacy import panel_util + from weave.legacy.weave import panel_util table_obj = panels.Table( panel_util.make_node( @@ -321,12 +321,12 @@ def _perform_post_persist_assertions( expected_uri_with_branch = f"wandb-artifact:///{target_entity}/{target_project_name}/{target_artifact_name}:{branch_name}/obj" # Data Assertions - get_op = weave.legacy.ops.get(expected_uri_with_hash) + get_op = weave.legacy.weave.ops.get(expected_uri_with_hash) get_res = weave.use(get_op) assert p_ref.obj == get_res == data # Only check branch if expected_uri_with_branch is not None if expected_uri_with_branch: - get_op = weave.legacy.ops.get(expected_uri_with_branch) + get_op = weave.legacy.weave.ops.get(expected_uri_with_branch) get_res = weave.use(get_op) assert get_res == data @@ -379,7 +379,7 @@ def test_mutate_remote_with_commit_hash(user_by_api_key_in_env): assert p_ref.version == expected_commit_hash - obj = weave.legacy.ops.get(p_ref.uri)[0] + obj = weave.legacy.weave.ops.get(p_ref.uri)[0] obj.set(new_data[0]) new_branch_name = f"user-{p_ref.version}" new_uri = f"local-artifact:///{target_artifact_name}:{new_branch_name}/obj" @@ -422,7 +422,7 @@ def test_mutate_remote_with_branch(user_by_api_key_in_env): assert p_ref.version == expected_commit_hash assert branch_name in p_ref.branch_uri - obj = weave.legacy.ops.get(p_ref.branch_uri)[0] + obj = weave.legacy.weave.ops.get(p_ref.branch_uri)[0] obj.set(new_data[0]) new_branch_name = f"user-{branch_name}" new_p_ref = LocalArtifactRef.from_str( @@ -461,7 +461,7 @@ def test_mutate_local_with_commit_hash(user_by_api_key_in_env, new_branch_name=N assert p_ref.version == expected_commit_hash - obj = weave.legacy.ops.get(p_ref.uri)[0] + obj = weave.legacy.weave.ops.get(p_ref.uri)[0] if new_branch_name == None: obj.set(new_data[0]) new_branch_name = new_commit_hash @@ -513,14 +513,14 @@ def test_mutate_local_with_commit_hash_and_branchpoint( assert p_ref.version == expected_commit_hash assert branch_name in p_ref.branch_uri - obj = weave.legacy.ops.get(p_ref.branch_uri)[0] + obj = weave.legacy.weave.ops.get(p_ref.branch_uri)[0] obj.set("PLACEHOLDER") mod_ref = LocalArtifactRef.from_uri( WeaveLocalArtifactURI.parse( f"local-artifact:///{target_artifact_name}:user-{branch_name}/obj" ) ) - obj = weave.legacy.ops.get(mod_ref.branch_uri)[0] + obj = weave.legacy.weave.ops.get(mod_ref.branch_uri)[0] if new_branch_name == None: obj.set(new_data[0]) branch_name = None @@ -571,7 +571,7 @@ def test_mutate_local_with_branch(user_by_api_key_in_env, new_branch_name=None): assert p_ref.version == expected_commit_hash - obj = weave.legacy.ops.get(p_ref.branch_uri)[0] + obj = weave.legacy.weave.ops.get(p_ref.branch_uri)[0] if new_branch_name == None: obj.set(new_data[0]) new_branch_name = new_commit_hash @@ -622,9 +622,9 @@ def test_mutate_local_with_branch_and_branchpoint(user_by_api_key_in_env): assert p_ref.version == expected_commit_hash assert branch_name in p_ref.branch_uri - obj = weave.legacy.ops.get(p_ref.branch_uri)[0] + obj = weave.legacy.weave.ops.get(p_ref.branch_uri)[0] obj.set("PLACEHOLDER") - obj = weave.legacy.ops.get( + obj = weave.legacy.weave.ops.get( f"local-artifact:///{target_artifact_name}:user-{branch_name}/obj" )[0] obj.set(new_data[0]) @@ -655,8 +655,8 @@ def test_merge_from_local_with_commit_hash_onto_remote_with_commit_hash( ): test_mutate_remote_with_commit_hash(user_by_api_key_in_env) - merged_uri = weave.legacy.ops.merge_artifact( - weave.legacy.ops.get("local-artifact:///test_artifact:61f78c8877df22942d23/obj") + merged_uri = weave.legacy.weave.ops.merge_artifact( + weave.legacy.weave.ops.get("local-artifact:///test_artifact:61f78c8877df22942d23/obj") ) assert merged_uri.startswith("wandb-artifact://") @@ -681,8 +681,8 @@ def test_merge_from_local_with_commit_hash_onto_remote_with_branch( ): test_mutate_remote_with_branch(user_by_api_key_in_env) - merged_uri = weave.legacy.ops.merge_artifact( - weave.legacy.ops.get("local-artifact:///test_artifact:61f78c8877df22942d23/obj") + merged_uri = weave.legacy.weave.ops.merge_artifact( + weave.legacy.weave.ops.get("local-artifact:///test_artifact:61f78c8877df22942d23/obj") ) assert merged_uri.startswith("wandb-artifact://") @@ -707,8 +707,8 @@ def test_merge_from_local_with_commit_hash_onto_local_with_commit_hash( ): test_mutate_local_with_commit_hash(user_by_api_key_in_env, "new_branch_name") - merged_uri = weave.legacy.ops.merge_artifact( - weave.legacy.ops.get("local-artifact:///test_artifact:61f78c8877df22942d23/obj") + merged_uri = weave.legacy.weave.ops.merge_artifact( + weave.legacy.weave.ops.get("local-artifact:///test_artifact:61f78c8877df22942d23/obj") ) assert merged_uri.startswith("local-artifact://") @@ -739,8 +739,8 @@ def test_merge_from_local_with_commit_hash_onto_local_with_branch( ): test_mutate_local_with_branch(user_by_api_key_in_env, "new_branch_name") - merged_uri = weave.legacy.ops.merge_artifact( - weave.legacy.ops.get("local-artifact:///test_artifact:61f78c8877df22942d23/obj") + merged_uri = weave.legacy.weave.ops.merge_artifact( + weave.legacy.weave.ops.get("local-artifact:///test_artifact:61f78c8877df22942d23/obj") ) assert merged_uri.startswith("local-artifact://") @@ -765,8 +765,8 @@ def test_merge_from_local_with_commit_hash_onto_local_with_branch_and_branchpoin user_by_api_key_in_env, "new_branch_name" ) - merged_uri = weave.legacy.ops.merge_artifact( - weave.legacy.ops.get("local-artifact:///test_artifact:61f78c8877df22942d23/obj") + merged_uri = weave.legacy.weave.ops.merge_artifact( + weave.legacy.weave.ops.get("local-artifact:///test_artifact:61f78c8877df22942d23/obj") ) assert merged_uri.startswith("local-artifact://") @@ -788,7 +788,7 @@ def test_merge_from_local_with_commit_hash_onto_local_with_branch_and_branchpoin ), ) - merged_uri_2 = weave.legacy.ops.merge_artifact(weave.legacy.ops.get(merged_uri)) + merged_uri_2 = weave.legacy.weave.ops.merge_artifact(weave.legacy.weave.ops.get(merged_uri)) new_p_ref_2 = WandbArtifactRef.from_str(merged_uri_2) assert merged_uri_2.startswith("wandb-artifact://") @@ -816,8 +816,8 @@ def test_merge_from_local_with_branch_onto_remote_with_commit_hash( ): test_mutate_remote_with_commit_hash(user_by_api_key_in_env) - merged_uri = weave.legacy.ops.merge_artifact( - weave.legacy.ops.get( + merged_uri = weave.legacy.weave.ops.merge_artifact( + weave.legacy.weave.ops.get( "local-artifact:///test_artifact:user-74d5ba98aca469b59e18/obj" ) ) @@ -842,8 +842,8 @@ def test_merge_from_local_with_branch_onto_remote_with_commit_hash( def test_merge_from_local_with_branch_onto_remote_with_branch(user_by_api_key_in_env): test_mutate_remote_with_branch(user_by_api_key_in_env) - merged_uri = weave.legacy.ops.merge_artifact( - weave.legacy.ops.get("local-artifact:///test_artifact:user-remote_branch/obj") + merged_uri = weave.legacy.weave.ops.merge_artifact( + weave.legacy.weave.ops.get("local-artifact:///test_artifact:user-remote_branch/obj") ) assert merged_uri.startswith("wandb-artifact://") @@ -868,8 +868,8 @@ def test_merge_from_local_with_branch_onto_local_with_commit_hash( ): test_mutate_local_with_commit_hash(user_by_api_key_in_env, "new_branch_name") - merged_uri = weave.legacy.ops.merge_artifact( - weave.legacy.ops.get("local-artifact:///test_artifact:new_branch_name/obj") + merged_uri = weave.legacy.weave.ops.merge_artifact( + weave.legacy.weave.ops.get("local-artifact:///test_artifact:new_branch_name/obj") ) assert merged_uri.startswith("local-artifact://") @@ -898,8 +898,8 @@ def test_merge_from_local_with_branch_onto_local_with_commit_hash( def test_merge_from_local_with_branch_onto_local_with_branch(user_by_api_key_in_env): test_mutate_local_with_branch(user_by_api_key_in_env, "new_branch_name") - merged_uri = weave.legacy.ops.merge_artifact( - weave.legacy.ops.get("local-artifact:///test_artifact:new_branch_name/obj") + merged_uri = weave.legacy.weave.ops.merge_artifact( + weave.legacy.weave.ops.get("local-artifact:///test_artifact:new_branch_name/obj") ) assert merged_uri.startswith("local-artifact://") @@ -924,8 +924,8 @@ def test_merge_from_local_with_branch_onto_local_with_branch_and_branchpoint( user_by_api_key_in_env, "new_branch_name" ) - merged_uri = weave.legacy.ops.merge_artifact( - weave.legacy.ops.get("local-artifact:///test_artifact:new_branch_name/obj") + merged_uri = weave.legacy.weave.ops.merge_artifact( + weave.legacy.weave.ops.get("local-artifact:///test_artifact:new_branch_name/obj") ) assert merged_uri.startswith("local-artifact://") @@ -947,7 +947,7 @@ def test_merge_from_local_with_branch_onto_local_with_branch_and_branchpoint( ), ) - merged_uri_2 = weave.legacy.ops.merge_artifact(weave.legacy.ops.get(merged_uri)) + merged_uri_2 = weave.legacy.weave.ops.merge_artifact(weave.legacy.weave.ops.get(merged_uri)) new_p_ref_2 = WandbArtifactRef.from_str(merged_uri_2) assert merged_uri_2.startswith("wandb-artifact://") @@ -976,7 +976,7 @@ def test_publish_saved_node(user_by_api_key_in_env): assert saved.from_op.inputs["uri"].val.startswith("local-artifact://") assert weave.use(saved) == data - published_art_uri = weave.legacy.ops.publish_artifact(saved, "my_list", None, None) + published_art_uri = weave.legacy.weave.ops.publish_artifact(saved, "my_list", None, None) assert published_art_uri.startswith("wandb-artifact://") assert weave.use(weave.get(published_art_uri)) == data @@ -997,7 +997,7 @@ def _replace_uri_version(uri: str, new_version: str) -> str: return uri.replace(_get_uri_version(uri), new_version) -def _get_uri_from_get_node(node: weave.legacy.graph.Node) -> str: +def _get_uri_from_get_node(node: weave.legacy.weave.graph.Node) -> str: return node.from_op.inputs["uri"].val # type: ignore @@ -1011,44 +1011,44 @@ def test_end_to_end_save_and_publish_flow(user_by_api_key_in_env): assert weave.use(saved_node) == data # Step 2: Make a branching mutation. - weave.legacy.ops.set( + weave.legacy.weave.ops.set( saved_node[0], "test_publish_saved_node_execution_2", root_args={"branch": "my-branch"}, ) branched_data = ["test_publish_saved_node_execution_2"] branched_uri = saved_uri.replace(saved_version, "my-branch") - branched_node = weave.legacy.ops.get(branched_uri) + branched_node = weave.legacy.weave.ops.get(branched_uri) assert weave.use(branched_node) == branched_data # Step 3: Merge the change back into the main branch. - merged_uri = weave.legacy.ops.merge_artifact(branched_node) + merged_uri = weave.legacy.weave.ops.merge_artifact(branched_node) assert merged_uri.startswith("local-artifact://") assert merged_uri != saved_uri != branched_uri - merged_node = weave.legacy.ops.get(merged_uri) + merged_node = weave.legacy.weave.ops.get(merged_uri) assert weave.use(merged_node) == branched_data # Step 4: Publish the new version remotely - published_uri = weave.legacy.ops.publish_artifact( + published_uri = weave.legacy.weave.ops.publish_artifact( merged_node, "my_list", None, None ) assert published_uri.startswith("wandb-artifact://") assert weave.use(weave.get(published_uri)) == branched_data # Step 5: Modify the remote version - weave.legacy.ops.set( - weave.legacy.ops.get(published_uri)[0], + weave.legacy.weave.ops.set( + weave.legacy.weave.ops.get(published_uri)[0], "test_publish_saved_node_execution_3", root_args={"branch": "my-branch-2"}, ) published_branched_data = ["test_publish_saved_node_execution_3"] published_branched_uri = "local-artifact:///my_list:my-branch-2/obj" - published_branched_node = weave.legacy.ops.get(published_branched_uri) + published_branched_node = weave.legacy.weave.ops.get(published_branched_uri) assert weave.use(published_branched_node) == published_branched_data # Step 6: Merge the remote change back into the main branch. - published_merged_uri = weave.legacy.ops.merge_artifact(published_branched_node) + published_merged_uri = weave.legacy.weave.ops.merge_artifact(published_branched_node) assert published_merged_uri.startswith("wandb-artifact://") assert published_merged_uri != published_uri - published_merged_node = weave.legacy.ops.get(published_merged_uri) + published_merged_node = weave.legacy.weave.ops.get(published_merged_uri) assert weave.use(published_merged_node) == published_branched_data diff --git a/weave/tests/legacy/test_pydantic.py b/weave/legacy/tests/test_pydantic.py similarity index 97% rename from weave/tests/legacy/test_pydantic.py rename to weave/legacy/tests/test_pydantic.py index 1a57eda18cb..720ec1c1d0b 100644 --- a/weave/tests/legacy/test_pydantic.py +++ b/weave/legacy/tests/test_pydantic.py @@ -3,7 +3,7 @@ import pydantic import weave -from weave.legacy import weave_types as types +from weave.legacy.weave import weave_types as types def test_pydantic_type_inference(): diff --git a/weave/tests/legacy/test_ref_tracking.py b/weave/legacy/tests/test_ref_tracking.py similarity index 92% rename from weave/tests/legacy/test_ref_tracking.py rename to weave/legacy/tests/test_ref_tracking.py index 47d8ca60408..61b97d769be 100644 --- a/weave/tests/legacy/test_ref_tracking.py +++ b/weave/legacy/tests/test_ref_tracking.py @@ -1,6 +1,6 @@ import weave -from weave.legacy import storage -from weave.legacy import weave_types as types +from weave.legacy.weave import storage +from weave.legacy.weave import weave_types as types def test_reffed_type(ref_tracking): diff --git a/weave/tests/legacy/test_refs.py b/weave/legacy/tests/test_refs.py similarity index 99% rename from weave/tests/legacy/test_refs.py rename to weave/legacy/tests/test_refs.py index ac9923eaaad..98bd7048bc5 100644 --- a/weave/tests/legacy/test_refs.py +++ b/weave/legacy/tests/test_refs.py @@ -2,8 +2,8 @@ import weave from weave.flow.obj import Object -from weave.legacy import artifact_local -from weave.legacy import ops_arrow as arrow +from weave.legacy.weave import artifact_local, storage +from weave.legacy.weave import ops_arrow as arrow from weave.trace import ref_util from weave.trace_server.refs_internal import ( DICT_KEY_EDGE_NAME, @@ -11,8 +11,6 @@ OBJECT_ATTR_EDGE_NAME, ) -from ... import storage - def test_laref_artifact_version_1(): art = artifact_local.LocalArtifact("my-art", "v19") diff --git a/weave/tests/legacy/test_relpath_no_syscalls.py b/weave/legacy/tests/test_relpath_no_syscalls.py similarity index 97% rename from weave/tests/legacy/test_relpath_no_syscalls.py rename to weave/legacy/tests/test_relpath_no_syscalls.py index 412aed2b06e..66ce63b1af0 100644 --- a/weave/tests/legacy/test_relpath_no_syscalls.py +++ b/weave/legacy/tests/test_relpath_no_syscalls.py @@ -1,6 +1,6 @@ import pytest -from weave.legacy.util import relpath_no_syscalls +from weave.legacy.weave.util import relpath_no_syscalls @pytest.mark.parametrize( diff --git a/weave/tests/legacy/test_run_segment.py b/weave/legacy/tests/test_run_segment.py similarity index 98% rename from weave/tests/legacy/test_run_segment.py rename to weave/legacy/tests/test_run_segment.py index eb0f36f81c6..cf918473b88 100644 --- a/weave/tests/legacy/test_run_segment.py +++ b/weave/legacy/tests/test_run_segment.py @@ -6,12 +6,10 @@ import pytest import weave -from weave.legacy import ops -from weave.legacy import weave_types as types -from weave.legacy.ops_arrow import ArrowWeaveList, arrow_as_array -from weave.legacy.ops_domain.run_segment import RunSegment - -from ... import api, storage, weave_internal +from weave.legacy.weave import api, ops, storage, weave_internal +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.ops_arrow import ArrowWeaveList, arrow_as_array +from weave.legacy.weave.ops_domain.run_segment import RunSegment N_NUMERIC_METRICS = 99 # number of numerical columns in the metrics table diff --git a/weave/tests/legacy/test_serialize.py b/weave/legacy/tests/test_serialize.py similarity index 94% rename from weave/tests/legacy/test_serialize.py rename to weave/legacy/tests/test_serialize.py index 030ddb13465..4a7869e9fe4 100644 --- a/weave/tests/legacy/test_serialize.py +++ b/weave/legacy/tests/test_serialize.py @@ -1,14 +1,13 @@ import pytest import weave -from weave import weave_internal -from weave.legacy import api as api -from weave.legacy import graph, op_args, ops, registry_mem, serialize -from weave.legacy import weave_types as types -from weave.legacy.ops_primitives import list_ -from weave.weave_internal import make_const_node - -from .. import fixture_fakewandb as fwb +from weave.legacy.weave import api as api +from weave.legacy.weave import graph, op_args, ops, registry_mem, serialize, weave_internal +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.ops_primitives import list_ +from weave.legacy.weave.weave_internal import make_const_node + +from ...tests import fixture_fakewandb as fwb response = { "project_518fa79465d8ffaeb91015dce87e092f": { diff --git a/weave/tests/legacy/test_show.py b/weave/legacy/tests/test_show.py similarity index 90% rename from weave/tests/legacy/test_show.py rename to weave/legacy/tests/test_show.py index bee6ecd76dd..8d56c86cd0c 100644 --- a/weave/tests/legacy/test_show.py +++ b/weave/legacy/tests/test_show.py @@ -2,9 +2,9 @@ # The outputs of weave.show (the generated panel urls and their arguments) # need to match what javascript expects. -from weave.legacy import ops +from weave.legacy.weave import ops -from ...legacy.show import _show_params +from ...legacy.weave.show import _show_params from . import test_helpers diff --git a/weave/tests/legacy/test_stitch.py b/weave/legacy/tests/test_stitch.py similarity index 86% rename from weave/tests/legacy/test_stitch.py rename to weave/legacy/tests/test_stitch.py index 368f01ce553..12e6deb26a5 100644 --- a/weave/tests/legacy/test_stitch.py +++ b/weave/legacy/tests/test_stitch.py @@ -3,14 +3,13 @@ import pytest import weave -from weave.legacy import compile_domain, compile_table -from weave.legacy import context_state as _context -from weave.legacy.language_features.tagging import make_tag_getter_op -from weave.legacy.ops_domain import run_ops - -from ... import weave_internal -from ...legacy import stitch -from .. import fixture_fakewandb as fwb +from weave.legacy.weave import compile_domain, compile_table, weave_internal +from weave.legacy.weave import context_state as _context +from weave.legacy.weave.language_features.tagging import make_tag_getter_op +from weave.legacy.weave.ops_domain import run_ops + +from ...legacy.weave import stitch +from ...tests import fixture_fakewandb as fwb from . import test_wb _loading_builtins_token = _context.set_loading_built_ins() @@ -123,7 +122,7 @@ def test_tag_access_in_filter_expr(): def test_traverse_dict(): obj_node = weave.save(_TestPlanObject("a", 1)) - p = stitch.stitch([weave.legacy.ops.dict_(x=obj_node)["x"].horse()]) + p = stitch.stitch([weave.legacy.weave.ops.dict_(x=obj_node)["x"].horse()]) obj_recorder = p.get_result(obj_node) assert len(obj_recorder.calls) == 1 assert obj_recorder.calls[0].node.from_op.name == "_TestPlanObject-horse" @@ -131,7 +130,7 @@ def test_traverse_dict(): def test_travese_groupby_dict(): obj_node = weave.save([{"o": {"a": 5}, "x": 1}]) - grouped = obj_node.groupby(lambda row: weave.legacy.ops.dict_(x=row["o"])) + grouped = obj_node.groupby(lambda row: weave.legacy.weave.ops.dict_(x=row["o"])) output = grouped[0]["x"] groupkey_output = grouped[0].groupkey()["x"]["a"] p = stitch.stitch([output, groupkey_output]) @@ -164,16 +163,16 @@ def test_zero_arg_ops(): def test_shared_fn_node(): - const_list_node = weave.legacy.ops.make_list(a=1, b=2) + const_list_node = weave.legacy.weave.ops.make_list(a=1, b=2) indexed_node = const_list_node[0] - arr_1_node = weave.legacy.ops.make_list(a=1, b=2, c=3) - arr_2_node = weave.legacy.ops.make_list(a=10, b=20, c=30) + arr_1_node = weave.legacy.weave.ops.make_list(a=1, b=2, c=3) + arr_2_node = weave.legacy.weave.ops.make_list(a=10, b=20, c=30) mapped_1_node = arr_1_node.map( - lambda row: weave.legacy.ops.dict_(item=row, const=indexed_node) + lambda row: weave.legacy.weave.ops.dict_(item=row, const=indexed_node) ) mapped_2_node = arr_2_node.map( - lambda row: weave.legacy.ops.dict_(item=row, const=indexed_node) + lambda row: weave.legacy.weave.ops.dict_(item=row, const=indexed_node) ) mapped_1_item_node = mapped_1_node["item"] @@ -184,7 +183,7 @@ def test_shared_fn_node(): mapped_2_item_add_node = mapped_2_item_node + 100 mapped_2_const_add_node = mapped_2_const_node + 100 - list_of_list_node = weave.legacy.ops.make_list( + list_of_list_node = weave.legacy.weave.ops.make_list( a=mapped_1_item_node, b=mapped_1_const_node, c=mapped_2_item_add_node, @@ -219,9 +218,9 @@ def assert_node_calls(node, expected_call_names): def test_stitch_keytypes_override_fetch_all_columns(fake_wandb): fake_wandb.fake_api.add_mock(test_wb.table_mock_filtered) - keytypes_node = weave.legacy.ops.object_keytypes( + keytypes_node = weave.legacy.weave.ops.object_keytypes( run_ops.run_tag_getter_op( - weave.legacy.ops.project("stacey", "mendeleev") + weave.legacy.weave.ops.project("stacey", "mendeleev") .filteredRuns("{}", "-createdAt") .limit(50) .summary() @@ -256,15 +255,15 @@ def test_stitch_overlapping_tags(fake_wandb): } } ) - project_node = weave.legacy.ops.project("stacey", "mendeleev") + project_node = weave.legacy.weave.ops.project("stacey", "mendeleev") filtered_runs_a_node = project_node.filteredRuns("{}", "-createdAt")[0] summary_a_node = filtered_runs_a_node.summary() - tagged_name_a = weave.legacy.ops.run_ops.run_tag_getter_op( + tagged_name_a = weave.legacy.weave.ops.run_ops.run_tag_getter_op( summary_a_node["a"] ).name() filtered_runs_b_node = project_node.filteredRuns("{}", "+createdAt")[0] summary_b_node = filtered_runs_b_node.summary() - tagged_id_b = weave.legacy.ops.run_ops.run_tag_getter_op(summary_b_node).id() + tagged_id_b = weave.legacy.weave.ops.run_ops.run_tag_getter_op(summary_b_node).id() p = stitch.stitch([tagged_name_a, tagged_id_b]) @@ -274,9 +273,9 @@ def test_stitch_overlapping_tags(fake_wandb): def test_refine_history_type_included_in_gql(): - project_node = weave.legacy.ops.project("stacey", "mendeleev") + project_node = weave.legacy.weave.ops.project("stacey", "mendeleev") runs_node = project_node.runs() - map_node = runs_node.map(lambda row: weave.legacy.ops.dict_(variant=row)) + map_node = runs_node.map(lambda row: weave.legacy.weave.ops.dict_(variant=row)) checkpoint_node = map_node.createIndexCheckpointTag() index_node = checkpoint_node[0] pick_node = index_node["variant"] @@ -287,7 +286,7 @@ def test_refine_history_type_included_in_gql(): def test_stitch_missing_key(): a_node = weave_internal.make_const_node(weave.types.String(), "a") - dict_node = weave.legacy.ops.dict_(a=a_node) + dict_node = weave.legacy.weave.ops.dict_(a=a_node) picked_valid = dict_node["a"] + "-suffix" picked_missing = dict_node["b"] + "-suffix" diff --git a/weave/tests/legacy/test_storage.py b/weave/legacy/tests/test_storage.py similarity index 96% rename from weave/tests/legacy/test_storage.py rename to weave/legacy/tests/test_storage.py index 4c31c43f9b7..8183200fa3e 100644 --- a/weave/tests/legacy/test_storage.py +++ b/weave/legacy/tests/test_storage.py @@ -7,14 +7,19 @@ import pytest import wandb -from weave.legacy import artifact_mem, artifact_wandb, mappers_python, ops_arrow -from weave.legacy import weave_types as types -from weave.legacy.arrow import list_ as arrow - -from ... import api as weave -from ... import storage -from ...legacy.weavejs_fixes import recursively_unwrap_unions -from ...weave_internal import make_const_node +from weave.legacy.weave import api as weave +from weave.legacy.weave import ( + artifact_mem, + artifact_wandb, + mappers_python, + ops_arrow, + storage, +) +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.arrow import list_ as arrow +from weave.legacy.weave.weave_internal import make_const_node + +from ...legacy.weave.weavejs_fixes import recursively_unwrap_unions from . import test_helpers diff --git a/weave/tests/stream_table/test_stream_table.py b/weave/legacy/tests/test_stream_table.py similarity index 89% rename from weave/tests/stream_table/test_stream_table.py rename to weave/legacy/tests/test_stream_table.py index 393894a8aa3..7591331e979 100644 --- a/weave/tests/stream_table/test_stream_table.py +++ b/weave/legacy/tests/test_stream_table.py @@ -1,7 +1,7 @@ import os import time -from weave.legacy.wandb_interface.wandb_stream_table import StreamTable +from weave.legacy.weave.wandb_interface.wandb_stream_table import StreamTable def main(table_name, project_name, entity_name, sample_limit, gap_ms): diff --git a/weave/tests/legacy/test_table_ops.py b/weave/legacy/tests/test_table_ops.py similarity index 95% rename from weave/tests/legacy/test_table_ops.py rename to weave/legacy/tests/test_table_ops.py index 9296cb2e235..2994235eb37 100644 --- a/weave/tests/legacy/test_table_ops.py +++ b/weave/legacy/tests/test_table_ops.py @@ -3,13 +3,20 @@ import pytest -from weave.legacy import box, context, context_state, graph, ops -from weave.legacy import weave_types as types -from weave.legacy.ops_domain import table as table_ops +from weave.legacy.weave import api as weave +from weave.legacy.weave import ( + box, + context, + context_state, + graph, + ops, + storage, + weave_internal, +) +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.ops_domain import table as table_ops -from ... import api as weave -from ... import storage, weave_internal -from .. import weavejs_ops +from weave.legacy.tests.util import weavejs_ops TABLE_TYPES = ["list", "pandas", "sql"] diff --git a/weave/tests/legacy/test_tagging.py b/weave/legacy/tests/test_tagging.py similarity index 95% rename from weave/tests/legacy/test_tagging.py rename to weave/legacy/tests/test_tagging.py index fa691cdd77a..471205b4920 100644 --- a/weave/tests/legacy/test_tagging.py +++ b/weave/legacy/tests/test_tagging.py @@ -1,21 +1,19 @@ import pytest import weave -from weave.legacy import box -from weave.legacy import weave_types as types -from weave.legacy.artifact_fs import FilesystemArtifactFileType -from weave.legacy.language_features.tagging import ( +from weave.legacy.weave import box, weave_internal +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.artifact_fs import FilesystemArtifactFileType +from weave.legacy.weave.language_features.tagging import ( make_tag_getter_op, tag_store, tagged_value_type, tagged_value_type_helpers, ) -from weave.legacy.ops_domain.run_ops import run_tag_getter_op -from weave.legacy.ops_domain.wb_domain_types import ProjectType, Run, RunType -from weave.legacy.ops_primitives import dict as dict_ops -from weave.legacy.ops_primitives import list_ as list_ops - -from ... import weave_internal +from weave.legacy.weave.ops_domain.run_ops import run_tag_getter_op +from weave.legacy.weave.ops_domain.wb_domain_types import ProjectType, Run, RunType +from weave.legacy.weave.ops_primitives import dict as dict_ops +from weave.legacy.weave.ops_primitives import list_ as list_ops def test_tagged_value(): @@ -31,7 +29,7 @@ def test_tagged_types(): class _TestNumber: inner: int - from weave.legacy import context_state + from weave.legacy.weave import context_state _loading_builtins_token = context_state.set_loading_built_ins() diff --git a/weave/tests/legacy/test_templates.py b/weave/legacy/tests/test_templates.py similarity index 83% rename from weave/tests/legacy/test_templates.py rename to weave/legacy/tests/test_templates.py index 76335e6f118..6c3e2289295 100644 --- a/weave/tests/legacy/test_templates.py +++ b/weave/legacy/tests/test_templates.py @@ -3,10 +3,10 @@ import wandb import weave -from weave.legacy import context_state as _context -from weave.legacy.wandb_interface.wandb_stream_table import StreamTable +from weave.legacy.weave import context_state as _context +from weave.legacy.weave.wandb_interface.wandb_stream_table import StreamTable -from ...legacy.panels_py import generator_templates +from ...legacy.weave.panels_py import generator_templates _loading_builtins_token = _context.set_loading_built_ins() @@ -34,17 +34,17 @@ class DummyBoardConfig: def dummy_board( input_node, config: typing.Optional[DummyBoardConfig] = None, -) -> weave.legacy.panels.Group: +) -> weave.legacy.weave.panels.Group: assert board_input_type.assign_type(input_node.type) control_items = [ - weave.legacy.panels.GroupPanel( + weave.legacy.weave.panels.GroupPanel( input_node, id="data", ), ] - return weave.legacy.panels.Board(vars=control_items, panels=[]) + return weave.legacy.weave.panels.Board(vars=control_items, panels=[]) generator_templates.template_registry.register( @@ -77,13 +77,13 @@ def _assert_valid_node_raw(node): output_group = weave.use(dummy_node) # Assert the out template is successfully generated - assert isinstance(output_group, weave.legacy.panels.Group) + assert isinstance(output_group, weave.legacy.weave.panels.Group) data_node = output_group.config.items["sidebar"].config.items["data"] # assert that the node sent to the generator is the same as the node that is used # Note: this is a heuristic, but probably close enough - assert isinstance(data_node, weave.legacy.graph.OutputNode) + assert isinstance(data_node, weave.legacy.weave.graph.OutputNode) assert data_node.from_op.name == node.from_op.name assert str(data_node) == str(node) @@ -102,7 +102,7 @@ def test_templates_for_run_logs_valid(user_by_api_key_in_env): run.finish() run_history_node = ( - weave.legacy.ops.project(run.entity, run.project).run(run.id).history() + weave.legacy.weave.ops.project(run.entity, run.project).run(run.id).history() ) assert_valid_node(run_history_node) @@ -114,7 +114,7 @@ def test_templates_for_run_logs_invalid(user_by_api_key_in_env): run.finish() run_history_node = ( - weave.legacy.ops.project(run.entity, run.project).run(run.id).history() + weave.legacy.weave.ops.project(run.entity, run.project).run(run.id).history() ) assert_invalid_node(run_history_node) @@ -126,7 +126,7 @@ def test_templates_for_logged_table_valid(user_by_api_key_in_env): run.finish() table_node = ( - weave.legacy.ops.project(run.entity, run.project) + weave.legacy.weave.ops.project(run.entity, run.project) .run(run.id) .summary()["table"] .table() @@ -142,7 +142,7 @@ def test_templates_for_logged_table_invalid(user_by_api_key_in_env): run.finish() table_node = ( - weave.legacy.ops.project(run.entity, run.project) + weave.legacy.weave.ops.project(run.entity, run.project) .run(run.id) .summary()["table"] .table() diff --git a/weave/tests/legacy/test_timestamp_bin.py b/weave/legacy/tests/test_timestamp_bin.py similarity index 84% rename from weave/tests/legacy/test_timestamp_bin.py rename to weave/legacy/tests/test_timestamp_bin.py index 1243c2d3a3a..1e24617baeb 100644 --- a/weave/tests/legacy/test_timestamp_bin.py +++ b/weave/legacy/tests/test_timestamp_bin.py @@ -6,7 +6,7 @@ def test_timestamp_bins_fixed(): - bin_fn = weave.legacy.ops.timestamp_bins_fixed(60) + bin_fn = weave.legacy.weave.ops.timestamp_bins_fixed(60) ts = datetime(2020, 1, 1, 8, 30, 1) called = bin_fn(ts) @@ -19,7 +19,7 @@ def test_timestamp_bins_fixed(): def test_timestamp_bins_nice(): start_ts = datetime(2020, 1, 1, 8, 30, 1) stop_ts = datetime(2020, 1, 1, 12, 30, 0) - bin_fn = weave.legacy.ops.timestamp_bins_nice([start_ts, stop_ts], 100) + bin_fn = weave.legacy.weave.ops.timestamp_bins_nice([start_ts, stop_ts], 100) ts = datetime(2020, 1, 1, 8, 45, 13) called = bin_fn(ts) @@ -32,7 +32,7 @@ def test_timestamp_bins_nice(): def test_timestamp_bin(): start_ts = datetime(2020, 1, 1, 8, 30, 1) stop_ts = datetime(2020, 1, 1, 12, 30, 0) - bin_fn = weave.legacy.ops.timestamp_bins_nice([start_ts, stop_ts], 100) + bin_fn = weave.legacy.weave.ops.timestamp_bins_nice([start_ts, stop_ts], 100) ts = datetime(2020, 1, 1, 8, 45, 13) ts_node = weave.save(ts) @@ -45,10 +45,10 @@ def test_timestamp_bin(): def test_timestamp_bin_vector(): start_ts = datetime(2020, 1, 1, 8, 30, 1) stop_ts = datetime(2020, 1, 1, 12, 30, 0) - bin_fn = weave.legacy.ops.timestamp_bins_nice([start_ts, stop_ts], 100) + bin_fn = weave.legacy.weave.ops.timestamp_bins_nice([start_ts, stop_ts], 100) ts = datetime(2020, 1, 1, 8, 45, 13, tzinfo=timezone.utc) - ts_node = weave.legacy.ops.to_weave_arrow([ts, ts + timedelta(seconds=90)]) + ts_node = weave.legacy.weave.ops.to_weave_arrow([ts, ts + timedelta(seconds=90)]) # This does not vectorize because mapped ops don't automatically # vectorize! diff --git a/weave/tests/legacy/test_trace.py b/weave/legacy/tests/test_trace.py similarity index 84% rename from weave/tests/legacy/test_trace.py rename to weave/legacy/tests/test_trace.py index e779f576e7e..a809e10a654 100644 --- a/weave/tests/legacy/test_trace.py +++ b/weave/legacy/tests/test_trace.py @@ -1,11 +1,10 @@ import re -from weave.legacy import graph +from weave.legacy.weave import api as weave +from weave.legacy.weave import graph, storage +from weave.legacy.weave.weave_internal import make_const_node -from ... import api as weave -from ... import storage -from ...legacy import trace_legacy -from ...weave_internal import make_const_node +from ...legacy.weave import trace_legacy def test_node_expr(): diff --git a/weave/tests/legacy/test_typeddict_notrequired.py b/weave/legacy/tests/test_typeddict_notrequired.py similarity index 87% rename from weave/tests/legacy/test_typeddict_notrequired.py rename to weave/legacy/tests/test_typeddict_notrequired.py index 0625b430aad..7b9c940184d 100644 --- a/weave/tests/legacy/test_typeddict_notrequired.py +++ b/weave/legacy/tests/test_typeddict_notrequired.py @@ -6,8 +6,8 @@ TypedDict, ) -from weave.legacy import infer_types -from weave.legacy import weave_types as types +from weave.legacy.weave import infer_types +from weave.legacy.weave import weave_types as types class _TestNotRequiredTypedDict(TypedDict): diff --git a/weave/tests/legacy/test_uris.py b/weave/legacy/tests/test_uris.py similarity index 91% rename from weave/tests/legacy/test_uris.py rename to weave/legacy/tests/test_uris.py index ace2efcc36d..f544d9158a5 100644 --- a/weave/tests/legacy/test_uris.py +++ b/weave/legacy/tests/test_uris.py @@ -1,7 +1,7 @@ import pytest -from weave.legacy import uris -from weave.legacy.artifact_wandb import WeaveWBArtifactURI +from weave.legacy.weave import uris +from weave.legacy.weave.artifact_wandb import WeaveWBArtifactURI URIS = [ "op-get", diff --git a/weave/tests/legacy/test_wb.py b/weave/legacy/tests/test_wb.py similarity index 98% rename from weave/tests/legacy/test_wb.py rename to weave/legacy/tests/test_wb.py index 065e4b0dc09..b6cc5a11d5d 100644 --- a/weave/tests/legacy/test_wb.py +++ b/weave/legacy/tests/test_wb.py @@ -6,21 +6,21 @@ import pytest import wandb -from weave.legacy import api as weave -from weave.legacy import artifact_fs, artifact_wandb, compile, graph, ops, stitch, uris -from weave.legacy import ops_arrow as arrow -from weave.legacy import weave_types as types -from weave.legacy.language_features.tagging.tagged_value_type import TaggedValueType -from weave.legacy.ops_arrow import ArrowWeaveListType -from weave.legacy.ops_domain import artifact_membership_ops as amo -from weave.legacy.ops_domain import table, wb_util, wbmedia -from weave.legacy.ops_domain import wb_domain_types as wdt -from weave.legacy.ops_primitives import dict_, list_ -from weave.legacy.ops_primitives.file import _as_w0_dict_ -from weave.tests.legacy.test_wb_domain_ops import assert_gql_str_equal - -from .. import fixture_fakewandb as fwb -from .. import weavejs_ops +from weave.legacy.weave import api as weave +from weave.legacy.weave import artifact_fs, artifact_wandb, compile, graph, ops, stitch, uris +from weave.legacy.weave import ops_arrow as arrow +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.language_features.tagging.tagged_value_type import TaggedValueType +from weave.legacy.weave.ops_arrow import ArrowWeaveListType +from weave.legacy.weave.ops_domain import artifact_membership_ops as amo +from weave.legacy.weave.ops_domain import table, wb_util, wbmedia +from weave.legacy.weave.ops_domain import wb_domain_types as wdt +from weave.legacy.weave.ops_primitives import dict_, list_ +from weave.legacy.weave.ops_primitives.file import _as_w0_dict_ +from .test_wb_domain_ops import assert_gql_str_equal + +from ...tests import fixture_fakewandb as fwb +from weave.legacy.tests.util import weavejs_ops file_path_response = { "project_518fa79465d8ffaeb91015dce87e092f": { diff --git a/weave/tests/legacy/test_wb_data_types.py b/weave/legacy/tests/test_wb_data_types.py similarity index 95% rename from weave/tests/legacy/test_wb_data_types.py rename to weave/legacy/tests/test_wb_data_types.py index 7f4c7fd66f1..aae7baafdc2 100644 --- a/weave/tests/legacy/test_wb_data_types.py +++ b/weave/legacy/tests/test_wb_data_types.py @@ -12,16 +12,16 @@ from wandb.sdk.data_types._dtypes import TypeRegistry as SDKTypeRegistry import weave -from weave.legacy import artifact_fs -from weave.legacy import weave_types as types -from weave.legacy.artifact_wandb import WandbArtifact, WeaveWBArtifactURI -from weave.legacy.language_features.tagging.tagged_value_type import TaggedValueType -from weave.legacy.ops_domain.wbmedia import ImageArtifactFileRefType -from weave.legacy.ops_primitives import file -from weave.legacy.wandb_client_api import wandb_gql_query -from weave.legacy.wandb_util import weave0_type_json_to_weave1_type +from weave.legacy.weave import artifact_fs +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.artifact_wandb import WandbArtifact, WeaveWBArtifactURI +from weave.legacy.weave.language_features.tagging.tagged_value_type import TaggedValueType +from weave.legacy.weave.ops_domain.wbmedia import ImageArtifactFileRefType +from weave.legacy.weave.ops_primitives import file +from weave.legacy.weave.wandb_client_api import wandb_gql_query +from weave.legacy.weave.wandb_util import weave0_type_json_to_weave1_type -from ..fixture_fakewandb import FakeApi +from ...tests.fixture_fakewandb import FakeApi class RandomClass: @@ -109,31 +109,31 @@ def make_molecule(clean_up=True): # ( make_image(), - weave.legacy.ops.ImageArtifactFileRef.WeaveType(), # type: ignore + weave.legacy.weave.ops.ImageArtifactFileRef.WeaveType(), # type: ignore ), ( make_audio(), - weave.legacy.ops.AudioArtifactFileRef.WeaveType(), # type: ignore + weave.legacy.weave.ops.AudioArtifactFileRef.WeaveType(), # type: ignore ), ( make_html(), - weave.legacy.ops.HtmlArtifactFileRef.WeaveType(), # type: ignore + weave.legacy.weave.ops.HtmlArtifactFileRef.WeaveType(), # type: ignore ), ( make_bokeh(), - weave.legacy.ops.BokehArtifactFileRef.WeaveType(), # type: ignore + weave.legacy.weave.ops.BokehArtifactFileRef.WeaveType(), # type: ignore ), ( make_video(), - weave.legacy.ops.VideoArtifactFileRef.WeaveType(), # type: ignore + weave.legacy.weave.ops.VideoArtifactFileRef.WeaveType(), # type: ignore ), ( make_object3d(), - weave.legacy.ops.Object3DArtifactFileRef.WeaveType(), # type: ignore + weave.legacy.weave.ops.Object3DArtifactFileRef.WeaveType(), # type: ignore ), ( make_molecule(), - weave.legacy.ops.MoleculeArtifactFileRef.WeaveType(), # type: ignore + weave.legacy.weave.ops.MoleculeArtifactFileRef.WeaveType(), # type: ignore ), # See comment in wandb_util.py - this may change in the future # Temporarily disabled until we can figure out how to mock @@ -630,7 +630,7 @@ def test_grouping_on_images(fake_wandb): table_node = file_node.table() table_rows = table_node.rows() grouped = table_rows.groupby( - lambda row: weave.legacy.ops.dict_(g_image=row["image"]) + lambda row: weave.legacy.weave.ops.dict_(g_image=row["image"]) ) raw_data = weave.use(grouped).to_pylist_notags() @@ -761,7 +761,7 @@ def make_join_table_row_nodes(fake_wandb): def test_join_all_on_images(fake_wandb): table_1_rows, table_2_rows = make_join_table_row_nodes(fake_wandb) - rows = weave.legacy.ops.make_list(a=table_1_rows, b=table_2_rows) + rows = weave.legacy.weave.ops.make_list(a=table_1_rows, b=table_2_rows) joined = rows.joinAll(lambda row: row["image"], True) @@ -855,12 +855,12 @@ def wait_for(): wait_for_x_times(wait_for) - run_node = weave.legacy.ops.project(run.entity, run.project).run(run.id) + run_node = weave.legacy.weave.ops.project(run.entity, run.project).run(run.id) for history_op_name in ["history3", "history"]: history_node = run_node._get_op(history_op_name)() mapped_node = history_node.map( - lambda row: weave.legacy.ops.dict_( + lambda row: weave.legacy.weave.ops.dict_( **{key: row[key] for key in log_dict.keys()} ) ) diff --git a/weave/tests/legacy/test_wb_domain_ops.py b/weave/legacy/tests/test_wb_domain_ops.py similarity index 98% rename from weave/tests/legacy/test_wb_domain_ops.py rename to weave/legacy/tests/test_wb_domain_ops.py index 5674b65204e..89c12c8260a 100644 --- a/weave/tests/legacy/test_wb_domain_ops.py +++ b/weave/legacy/tests/test_wb_domain_ops.py @@ -4,13 +4,13 @@ import wandb import weave -from weave.legacy import ops -from weave.legacy.language_features.tagging import tagged_value_type -from weave.legacy.ops_domain import wb_domain_types -from weave.legacy.ops_primitives import _dict_utils +from weave.legacy.weave import ops +from weave.legacy.weave.language_features.tagging import tagged_value_type +from weave.legacy.weave.ops_domain import wb_domain_types +from weave.legacy.weave.ops_primitives import _dict_utils -from ...legacy import registry_mem -from .. import fixture_fakewandb as fwb +from ...legacy.weave import registry_mem +from ...tests import fixture_fakewandb as fwb """ Tests in this file whould be used to test the graphs that can be constructed diff --git a/weave/tests/legacy/test_wb_domain_types.py b/weave/legacy/tests/test_wb_domain_types.py similarity index 88% rename from weave/tests/legacy/test_wb_domain_types.py rename to weave/legacy/tests/test_wb_domain_types.py index e2a131a57f2..5380e0347e3 100644 --- a/weave/tests/legacy/test_wb_domain_types.py +++ b/weave/legacy/tests/test_wb_domain_types.py @@ -1,8 +1,7 @@ -from weave.legacy import weave_types as types -from weave.legacy.ops_domain import wb_domain_types as wdt - -from ... import api as weave -from ... import storage +from weave.legacy.weave import api as weave +from weave.legacy.weave import storage +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.ops_domain import wb_domain_types as wdt def test_with_keys_assignability(): diff --git a/weave/tests/legacy/test_wb_end_to_end.py b/weave/legacy/tests/test_wb_end_to_end.py similarity index 83% rename from weave/tests/legacy/test_wb_end_to_end.py rename to weave/legacy/tests/test_wb_end_to_end.py index 2232079248d..2902a34d1aa 100644 --- a/weave/tests/legacy/test_wb_end_to_end.py +++ b/weave/legacy/tests/test_wb_end_to_end.py @@ -1,7 +1,7 @@ import wandb import weave -from weave.legacy import compile +from weave.legacy.weave import compile # Example of end to end integration test @@ -11,17 +11,17 @@ def test_run_logging(user_by_api_key_in_env): run.finish() summary_node = ( - weave.legacy.ops.project(run.entity, run.project).run(run.id).summary()["a"] + weave.legacy.weave.ops.project(run.entity, run.project).run(run.id).summary()["a"] ) summary = weave.use(summary_node) assert summary == 1 - is_none_node = weave.legacy.ops.project(run.entity, run.project).isNone() + is_none_node = weave.legacy.weave.ops.project(run.entity, run.project).isNone() assert weave.use(is_none_node) == False - is_none_node = weave.legacy.ops.project( + is_none_node = weave.legacy.weave.ops.project( run.entity, "project_does_not_exist" ).isNone() @@ -60,7 +60,7 @@ def _test_basic_publish(user_fixture): uri == f"wandb-artifact:///{user_fixture.username}/weave/list:0cdf3358dc939f961ca9/obj" ) - assert weave.legacy.ref_base.Ref.from_str(uri).get() == [1, 2, 3] + assert weave.legacy.weave.ref_base.Ref.from_str(uri).get() == [1, 2, 3] # Example of end to end integration test @@ -74,7 +74,7 @@ def test_run_histories(user_by_api_key_in_env): run.finish() history_node = ( - weave.legacy.ops.project(run.entity, run.project).runs().history().concat()["a"] + weave.legacy.weave.ops.project(run.entity, run.project).runs().history().concat()["a"] ) history = weave.use(history_node) @@ -89,7 +89,7 @@ def test_run_history_count(user_by_api_key_in_env, cache_mode_minimal): run.log({"a": 2}) run.finish() - run_node = weave.legacy.ops.project(run.entity, run.project).run(run.id) + run_node = weave.legacy.weave.ops.project(run.entity, run.project).run(run.id) h_count_node = run_node.history().count() history_count = weave.use(h_count_node) assert history_count == 2 diff --git a/weave/tests/legacy/test_wb_history_loading_compatability.py b/weave/legacy/tests/test_wb_history_loading_compatability.py similarity index 96% rename from weave/tests/legacy/test_wb_history_loading_compatability.py rename to weave/legacy/tests/test_wb_history_loading_compatability.py index 84edb18c1db..da2571fac1b 100644 --- a/weave/tests/legacy/test_wb_history_loading_compatability.py +++ b/weave/legacy/tests/test_wb_history_loading_compatability.py @@ -8,13 +8,13 @@ from PIL import Image import weave -from weave.legacy import context_state as _context -from weave.legacy.ops_domain.run_history.context import ( +from weave.legacy.weave import context_state as _context +from weave.legacy.weave.ops_domain.run_history.context import ( error_on_non_vectorized_history_transform, ) -from weave.legacy.ops_domain.run_history.history_op_common import _without_tags -from weave.legacy.wandb_client_api import wandb_gql_query -from weave.legacy.wandb_interface import wandb_stream_table +from weave.legacy.weave.ops_domain.run_history.history_op_common import _without_tags +from weave.legacy.weave.wandb_client_api import wandb_gql_query +from weave.legacy.weave.wandb_interface import wandb_stream_table HISTORY_OP_NAME = "history3" @@ -180,7 +180,7 @@ def do_batch_test(username, rows, do_assertion): row_accumulator, st, user_logged_keys = do_logging(username, rows) row_type = weave.types.TypeRegistry.type_of([{}, *row_accumulator]) - run_node = weave.legacy.ops.project(st._entity_name, st._project_name).run( + run_node = weave.legacy.weave.ops.project(st._entity_name, st._project_name).run( st._table_name ) @@ -392,7 +392,7 @@ def test_stream_table_perf(user_by_api_key_in_env, n_rows, n_cols): timings["log"] += time.time() print(f"Log Time: {timings['log']}") - run_node = weave.legacy.ops.project(st._entity_name, st._project_name).run( + run_node = weave.legacy.weave.ops.project(st._entity_name, st._project_name).run( st._table_name ) diff --git a/weave/tests/legacy/test_wb_stream_table.py b/weave/legacy/tests/test_wb_stream_table.py similarity index 91% rename from weave/tests/legacy/test_wb_stream_table.py rename to weave/legacy/tests/test_wb_stream_table.py index 68301ad4dde..55bf16953ff 100644 --- a/weave/tests/legacy/test_wb_stream_table.py +++ b/weave/legacy/tests/test_wb_stream_table.py @@ -5,8 +5,8 @@ from PIL import Image import weave -from weave.legacy import context, execute, gql_json_cache, wandb_api, weave_types -from weave.legacy.wandb_interface.wandb_stream_table import StreamTable +from weave.legacy.weave import context, execute, gql_json_cache, wandb_api, weave_types +from weave.legacy.weave.wandb_interface.wandb_stream_table import StreamTable def make_stream_table(*args, **kwargs): @@ -26,7 +26,7 @@ def test_stream_logging(user_by_api_key_in_env): st.finish() hist_node = ( - weave.legacy.ops.project(user_by_api_key_in_env.username, "stream-tables") + weave.legacy.weave.ops.project(user_by_api_key_in_env.username, "stream-tables") .run("test_table") .history3() ) @@ -56,7 +56,7 @@ def test_bytes_read_from_arrow_reporting(user_by_api_key_in_env): st.finish() hist_node = ( - weave.legacy.ops.project(user_by_api_key_in_env.username, "stream-tables") + weave.legacy.weave.ops.project(user_by_api_key_in_env.username, "stream-tables") .run("test_table") .history3() ) @@ -90,7 +90,7 @@ def image(): time.sleep(5) hist_node = ( - weave.legacy.ops.project(user_by_api_key_in_env.username, "stream-tables") + weave.legacy.weave.ops.project(user_by_api_key_in_env.username, "stream-tables") .run("test_table-8") .history2() ) @@ -121,7 +121,7 @@ def test_multi_writers_sequential(user_by_api_key_in_env): def do_asserts(): hist_node = ( - weave.legacy.ops.project(user_by_api_key_in_env.username, "stream-tables") + weave.legacy.weave.ops.project(user_by_api_key_in_env.username, "stream-tables") .run("test_table") .history2() ) @@ -181,7 +181,7 @@ def test_multi_writers_parallel(user_by_api_key_in_env): st_2.finish() hist_node = ( - weave.legacy.ops.project(entity_name, "stream-tables") + weave.legacy.weave.ops.project(entity_name, "stream-tables") .run(table_name) .history2() ) diff --git a/weave/tests/legacy/test_wb_tables.py b/weave/legacy/tests/test_wb_tables.py similarity index 88% rename from weave/tests/legacy/test_wb_tables.py rename to weave/legacy/tests/test_wb_tables.py index 98ccb6ed076..3dca70237be 100644 --- a/weave/tests/legacy/test_wb_tables.py +++ b/weave/legacy/tests/test_wb_tables.py @@ -4,12 +4,12 @@ import wandb import weave -from weave.legacy.language_features.tagging import make_tag_getter_op -from weave.legacy.language_features.tagging.tagged_value_type import TaggedValueType -from weave.legacy.ops_arrow.list_ops import filter -from weave.legacy.ops_domain import wbmedia -from weave.legacy.ops_domain.wandb_domain_gql import _make_alias -from weave.weave_internal import make_const_node +from weave.legacy.weave.language_features.tagging import make_tag_getter_op +from weave.legacy.weave.language_features.tagging.tagged_value_type import TaggedValueType +from weave.legacy.weave.ops_arrow.list_ops import filter +from weave.legacy.weave.ops_domain import wbmedia +from weave.legacy.weave.ops_domain.wandb_domain_gql import _make_alias +from weave.legacy.weave.weave_internal import make_const_node def use_static_artifact_node( @@ -18,7 +18,7 @@ def use_static_artifact_node( project_name="test_project", collection_name="joined_table_artifact", version="latest", -) -> weave.legacy.graph.Node: +) -> weave.legacy.weave.graph.Node: fake_wandb.fake_api.add_mock( lambda q, ndx: { "project_5702147f0293fd7538d402af13069708": { @@ -53,7 +53,7 @@ def use_static_artifact_node( } } ) - return weave.legacy.ops.project(entity_name, project_name).artifactVersion( + return weave.legacy.weave.ops.project(entity_name, project_name).artifactVersion( collection_name, version ) @@ -242,9 +242,9 @@ def test_metric_table_join(fake_wandb): file_node = art_node.file("table.table.json") table_node = file_node.table() table_rows = table_node.rows().createIndexCheckpointTag() - grouped = table_rows.groupby(lambda row: weave.legacy.ops.dict_(label=row["label"])) + grouped = table_rows.groupby(lambda row: weave.legacy.weave.ops.dict_(label=row["label"])) sorted = grouped.sort( - lambda row: weave.legacy.ops.make_list(a=row.groupkey()["label"]), ["asc"] + lambda row: weave.legacy.weave.ops.make_list(a=row.groupkey()["label"]), ["asc"] ) group_col_0 = sorted[0].groupkey()["label"] group_col_1 = sorted[1].groupkey()["label"] @@ -265,9 +265,9 @@ def test_empty_table(fake_wandb): table_node = file_node.table() table_rows = table_node.rows().createIndexCheckpointTag() filtered = filter(table_rows, lambda row: row["label"] == "cat") - grouped = filtered.groupby(lambda row: weave.legacy.ops.dict_(label=row["label"])) + grouped = filtered.groupby(lambda row: weave.legacy.weave.ops.dict_(label=row["label"])) sorted = grouped.sort( - lambda row: weave.legacy.ops.make_list(a=row.groupkey()["label"]), ["asc"] + lambda row: weave.legacy.weave.ops.make_list(a=row.groupkey()["label"]), ["asc"] ) res = weave.use(sorted) assert res.to_pylist_raw() == [] @@ -306,14 +306,14 @@ def test_join_group_combo(fake_wandb): art_2_node = fake_wandb.mock_artifact_as_node(art_2) table_1_rows = art_1_node.file("table_1.table.json").table().rows() table_2_rows = art_2_node.file("table_2.table.json").table().rows() - list_of_tables = weave.legacy.ops.make_list(a=table_1_rows, b=table_2_rows).dropna() + list_of_tables = weave.legacy.weave.ops.make_list(a=table_1_rows, b=table_2_rows).dropna() joined_tables = list_of_tables.joinAll( - lambda row: weave.legacy.ops.make_list(a=row["id"]), False + lambda row: weave.legacy.weave.ops.make_list(a=row["id"]), False ) indexed = joined_tables.createIndexCheckpointTag() - grouped = indexed.groupby(lambda row: weave.legacy.ops.dict_(label=row["label"][0])) + grouped = indexed.groupby(lambda row: weave.legacy.weave.ops.dict_(label=row["label"][0])) sorted = grouped.sort( - lambda row: weave.legacy.ops.make_list(label=row.groupkey()["label"]), + lambda row: weave.legacy.weave.ops.make_list(label=row.groupkey()["label"]), ["asc"], ) assert weave.use(sorted.count()) == 3 @@ -326,7 +326,7 @@ def test_join_group_combo(fake_wandb): join_obj = sorted[0].joinObj()[0] assert weave.use(join_obj) == [1.0] - from weave.legacy import context_state + from weave.legacy.weave import context_state _loading_builtins_token = context_state.set_loading_built_ins() tag_getter_op = make_tag_getter_op.make_tag_getter_op( @@ -360,7 +360,7 @@ def test_group_by_const(fake_wandb): art_1_node = fake_wandb.mock_artifact_as_node(art_1) table_1_rows = art_1_node.file("table_1.table.json").table().rows() grouped = table_1_rows.groupby( - lambda row: weave.legacy.ops.dict_( + lambda row: weave.legacy.weave.ops.dict_( a=make_const_node(weave.types.Boolean(), True) ) ) @@ -413,14 +413,14 @@ def test_column_sort(fake_wandb): for col in columns: sorted = rows.sort( - lambda row: weave.legacy.ops.make_list(label=row[col]), ["desc"] + lambda row: weave.legacy.weave.ops.make_list(label=row[col]), ["desc"] ) assert weave.use(sorted).to_pylist_notags() == [ dict(zip(columns, row)) for row in [row3, row2, row1] ] sorted = sorted.sort( - lambda row: weave.legacy.ops.make_list(label=row[col]), ["asc"] + lambda row: weave.legacy.weave.ops.make_list(label=row[col]), ["asc"] ) assert weave.use(sorted).to_pylist_notags() == [ dict(zip(columns, row)) for row in data @@ -428,7 +428,7 @@ def test_column_sort(fake_wandb): # Additional test sorting typed timestamps sorted = rows.sort( - lambda row: weave.legacy.ops.make_list(label=row["timestamp"].toTimestamp()), + lambda row: weave.legacy.weave.ops.make_list(label=row["timestamp"].toTimestamp()), ["desc"], ) assert weave.use(sorted).to_pylist_notags() == [ @@ -436,7 +436,7 @@ def test_column_sort(fake_wandb): ] sorted = sorted.sort( - lambda row: weave.legacy.ops.make_list(label=row["timestamp"].toTimestamp()), + lambda row: weave.legacy.weave.ops.make_list(label=row["timestamp"].toTimestamp()), ["asc"], ) assert weave.use(sorted).to_pylist_notags() == [ @@ -464,8 +464,8 @@ def test_group_avg_sort_combo(fake_wandb): art_1_node = fake_wandb.mock_artifact_as_node(art_1) rows = art_1_node.file("table_1.table.json").table().rows() - grouped = rows.groupby(lambda row: weave.legacy.ops.dict_(label=row["label"])) + grouped = rows.groupby(lambda row: weave.legacy.weave.ops.dict_(label=row["label"])) sorted = grouped.sort( - lambda row: weave.legacy.ops.make_list(label=row["score"].avg()), ["asc"] + lambda row: weave.legacy.weave.ops.make_list(label=row["score"].avg()), ["asc"] ) assert weave.use(sorted[2].groupkey()["label"]) == "C" diff --git a/weave/legacy/tests/test_weave_api.py b/weave/legacy/tests/test_weave_api.py new file mode 100644 index 00000000000..0e20c9acf37 --- /dev/null +++ b/weave/legacy/tests/test_weave_api.py @@ -0,0 +1,56 @@ +import os + +import pytest + +import weave +import weave.legacy.weave.context_state +import weave.legacy.weave.wandb_api +import weave.trace.weave_init + + +def test_create_list_rename_delete(): + os.environ["WEAVE_CACHE_MODE"] = "minimal" + + # create + art_node = weave.legacy.weave.ops.get("local-artifact:///my-data:latest/obj") + art_node.set("foo") + assert weave.use(art_node) == "foo" + + # list + arts = weave.use(weave.legacy.weave.ops.local_artifacts()) + assert len(arts) == 1 + assert arts[0].name == "my-data" + + # rename + weave.legacy.weave.ops.rename_artifact(art_node, "my-data2") + arts = weave.use(weave.legacy.weave.ops.local_artifacts()) + assert len(arts) == 1 + assert arts[0].name == "my-data2" + + # delete + art_node = weave.legacy.weave.ops.get("local-artifact:///my-data2:latest/obj") + weave.legacy.weave.ops.delete_artifact(art_node) + arts = weave.use(weave.legacy.weave.ops.local_artifacts()) + assert len(arts) == 0 + + +def test_weave_finish_unsets_client(client): + @weave.op + def foo(): + return 1 + + weave.trace.client_context.weave_client.set_weave_client_global(None) + weave.trace.weave_init._current_inited_client = ( + weave.trace.weave_init.InitializedClient(client) + ) + weave_client = weave.trace.weave_init._current_inited_client.client + assert weave.trace.weave_init._current_inited_client is not None + + foo() + assert len(list(weave_client.calls())) == 1 + + weave.finish() + + foo() + assert len(list(weave_client.calls())) == 1 + assert weave.trace.weave_init._current_inited_client is None diff --git a/weave/tests/legacy/test_weave_types.py b/weave/legacy/tests/test_weave_types.py similarity index 83% rename from weave/tests/legacy/test_weave_types.py rename to weave/legacy/tests/test_weave_types.py index 1c88c49d14f..070d1791465 100644 --- a/weave/tests/legacy/test_weave_types.py +++ b/weave/legacy/tests/test_weave_types.py @@ -3,12 +3,12 @@ import pytest import weave -import weave.legacy -import weave.legacy.weave_types -from weave.legacy import _dict_utils, runs -from weave.legacy import weave_types as types -from weave.legacy.language_features.tagging.tagged_value_type import TaggedValueType -from weave.legacy.ops_domain import wbmedia +import weave.legacy.weave +import weave.legacy.weave.weave_types +from weave.legacy.weave import _dict_utils, runs +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.language_features.tagging.tagged_value_type import TaggedValueType +from weave.legacy.weave.ops_domain import wbmedia from ... import errors @@ -155,23 +155,23 @@ def test_typeof_list_dict_merge(): def test_typeof_nested_dict_merge(): """Tests that nested merging is disabled.""" - t1 = weave.legacy.weave_types.TypedDict( - {"a": weave.legacy.weave_types.TypedDict({"b": types.Int()})} + t1 = weave.legacy.weave.weave_types.TypedDict( + {"a": weave.legacy.weave.weave_types.TypedDict({"b": types.Int()})} ) - t2 = weave.legacy.weave_types.TypedDict( - {"a": weave.legacy.weave_types.TypedDict({"c": types.String()})} + t2 = weave.legacy.weave.weave_types.TypedDict( + {"a": weave.legacy.weave.weave_types.TypedDict({"c": types.String()})} ) merged_type = _dict_utils.typeddict_merge_output_type({"self": t1, "other": t2}) - assert merged_type == weave.legacy.weave_types.TypedDict( - {"a": weave.legacy.weave_types.TypedDict({"c": types.String()})} + assert merged_type == weave.legacy.weave.weave_types.TypedDict( + {"a": weave.legacy.weave.weave_types.TypedDict({"c": types.String()})} ) def test_dict_without_key_type(): - fully_typed = weave.legacy.weave_types.TypeRegistry.type_from_dict( + fully_typed = weave.legacy.weave.weave_types.TypeRegistry.type_from_dict( {"type": "dict", "keyType": "string", "objectType": "number"} ) - partial_typed = weave.legacy.weave_types.TypeRegistry.type_from_dict( + partial_typed = weave.legacy.weave.weave_types.TypeRegistry.type_from_dict( {"type": "dict", "objectType": "number"} ) assert fully_typed.assign_type(partial_typed) @@ -179,24 +179,24 @@ def test_dict_without_key_type(): # def test_union_unknown(): # assert ( -# weave.legacy.weave_types.union( -# weave.legacy.weave_types.String(), weave.legacy.weave_types.UnknownType() +# weave.legacy.weave.weave_types.union( +# weave.legacy.weave.weave_types.String(), weave.legacy.weave.weave_types.UnknownType() # ) -# == weave.legacy.weave_types.String() +# == weave.legacy.weave.weave_types.String() # ) # assert ( -# weave.legacy.weave_types.union( -# weave.legacy.weave_types.UnknownType(), weave.legacy.weave_types.UnknownType() +# weave.legacy.weave.weave_types.union( +# weave.legacy.weave.weave_types.UnknownType(), weave.legacy.weave.weave_types.UnknownType() # ) -# == weave.legacy.weave_types.UnknownType() +# == weave.legacy.weave.weave_types.UnknownType() # ) # assert ( -# weave.legacy.weave_types.union( -# weave.legacy.weave_types.UnknownType(), -# weave.legacy.weave_types.UnknownType(), +# weave.legacy.weave.weave_types.union( +# weave.legacy.weave.weave_types.UnknownType(), +# weave.legacy.weave.weave_types.UnknownType(), # weave.types.String(), # ) -# == weave.legacy.weave_types.String() +# == weave.legacy.weave.weave_types.String() # ) @@ -204,78 +204,78 @@ def test_union_access(): ### Type return # Not all members have props - unioned = weave.legacy.weave_types.union( - weave.legacy.weave_types.String(), - weave.legacy.weave_types.List(weave.legacy.weave_types.String()), + unioned = weave.legacy.weave.weave_types.union( + weave.legacy.weave.weave_types.String(), + weave.legacy.weave.weave_types.List(weave.legacy.weave.weave_types.String()), ) with pytest.raises(AttributeError): unioned.object_type # Combined dicts - unioned = weave.legacy.weave_types.union( - weave.legacy.weave_types.List(weave.legacy.weave_types.String()), - weave.legacy.weave_types.List(weave.legacy.weave_types.Number()), + unioned = weave.legacy.weave.weave_types.union( + weave.legacy.weave.weave_types.List(weave.legacy.weave.weave_types.String()), + weave.legacy.weave.weave_types.List(weave.legacy.weave.weave_types.Number()), ) - assert unioned.object_type == weave.legacy.weave_types.union( - weave.legacy.weave_types.String(), weave.legacy.weave_types.Number() + assert unioned.object_type == weave.legacy.weave.weave_types.union( + weave.legacy.weave.weave_types.String(), weave.legacy.weave.weave_types.Number() ) # Nullable type - unioned = weave.legacy.weave_types.union( - weave.legacy.weave_types.NoneType(), - weave.legacy.weave_types.List(weave.legacy.weave_types.String()), + unioned = weave.legacy.weave.weave_types.union( + weave.legacy.weave.weave_types.NoneType(), + weave.legacy.weave.weave_types.List(weave.legacy.weave.weave_types.String()), ) - assert unioned.object_type == weave.legacy.weave_types.union( - weave.legacy.weave_types.String(), weave.legacy.weave_types.NoneType() + assert unioned.object_type == weave.legacy.weave.weave_types.union( + weave.legacy.weave.weave_types.String(), weave.legacy.weave.weave_types.NoneType() ) ### Dict Return # Not all members have props - unioned = weave.legacy.weave_types.union( - weave.legacy.weave_types.String(), - weave.legacy.weave_types.TypedDict({"a": weave.legacy.weave_types.String()}), + unioned = weave.legacy.weave.weave_types.union( + weave.legacy.weave.weave_types.String(), + weave.legacy.weave.weave_types.TypedDict({"a": weave.legacy.weave.weave_types.String()}), ) with pytest.raises(AttributeError): unioned.property_types # Combined dicts - unioned = weave.legacy.weave_types.union( - weave.legacy.weave_types.TypedDict( + unioned = weave.legacy.weave.weave_types.union( + weave.legacy.weave.weave_types.TypedDict( { - "same": weave.legacy.weave_types.Number(), - "solo_a": weave.legacy.weave_types.Number(), - "differ": weave.legacy.weave_types.Number(), + "same": weave.legacy.weave.weave_types.Number(), + "solo_a": weave.legacy.weave.weave_types.Number(), + "differ": weave.legacy.weave.weave_types.Number(), } ), - weave.legacy.weave_types.TypedDict( + weave.legacy.weave.weave_types.TypedDict( { - "same": weave.legacy.weave_types.Number(), - "solo_b": weave.legacy.weave_types.String(), - "differ": weave.legacy.weave_types.String(), + "same": weave.legacy.weave.weave_types.Number(), + "solo_b": weave.legacy.weave.weave_types.String(), + "differ": weave.legacy.weave.weave_types.String(), } ), ) assert unioned.property_types == { - "same": weave.legacy.weave_types.Number(), - "solo_a": weave.legacy.weave_types.union( - weave.legacy.weave_types.Number(), weave.legacy.weave_types.NoneType() + "same": weave.legacy.weave.weave_types.Number(), + "solo_a": weave.legacy.weave.weave_types.union( + weave.legacy.weave.weave_types.Number(), weave.legacy.weave.weave_types.NoneType() ), - "solo_b": weave.legacy.weave_types.union( - weave.legacy.weave_types.String(), weave.legacy.weave_types.NoneType() + "solo_b": weave.legacy.weave.weave_types.union( + weave.legacy.weave.weave_types.String(), weave.legacy.weave.weave_types.NoneType() ), - "differ": weave.legacy.weave_types.union( - weave.legacy.weave_types.Number(), weave.legacy.weave_types.String() + "differ": weave.legacy.weave.weave_types.union( + weave.legacy.weave.weave_types.Number(), weave.legacy.weave.weave_types.String() ), } # Nullable type - unioned = weave.legacy.weave_types.union( - weave.legacy.weave_types.NoneType(), - weave.legacy.weave_types.TypedDict({"a": weave.legacy.weave_types.String()}), + unioned = weave.legacy.weave.weave_types.union( + weave.legacy.weave.weave_types.NoneType(), + weave.legacy.weave.weave_types.TypedDict({"a": weave.legacy.weave.weave_types.String()}), ) assert unioned.property_types == { - "a": weave.legacy.weave_types.union( - weave.legacy.weave_types.String(), weave.legacy.weave_types.NoneType() + "a": weave.legacy.weave.weave_types.union( + weave.legacy.weave.weave_types.String(), weave.legacy.weave.weave_types.NoneType() ) } @@ -713,7 +713,7 @@ def test_deserializes_single_member_union(): def test_wbrun_not_assignable_to_weave_run(): - from weave.legacy.ops_domain import wb_domain_types + from weave.legacy.weave.ops_domain import wb_domain_types assert not weave.types.optional(wb_domain_types.Run().WeaveType()).assign_type( weave.types.RunType( diff --git a/weave/tests/legacy/test_weavejs_fixes.py b/weave/legacy/tests/test_weavejs_fixes.py similarity index 92% rename from weave/tests/legacy/test_weavejs_fixes.py rename to weave/legacy/tests/test_weavejs_fixes.py index 4488b4314ef..9f6cf60b1f7 100644 --- a/weave/tests/legacy/test_weavejs_fixes.py +++ b/weave/legacy/tests/test_weavejs_fixes.py @@ -2,10 +2,15 @@ import pytest -from weave.legacy import context_state, mappers_python, ops, weavejs_fixes -from weave.legacy import weave_types as types - -from ... import api, weave_internal +from weave.legacy.weave import ( + api, + context_state, + mappers_python, + ops, + weave_internal, + weavejs_fixes, +) +from weave.legacy.weave import weave_types as types @pytest.mark.skip( diff --git a/weave/tests/legacy/test_weavify.py b/weave/legacy/tests/test_weavify.py similarity index 92% rename from weave/tests/legacy/test_weavify.py rename to weave/legacy/tests/test_weavify.py index f5971099b43..9335160bdac 100644 --- a/weave/tests/legacy/test_weavify.py +++ b/weave/legacy/tests/test_weavify.py @@ -1,11 +1,10 @@ import pytest -from weave.legacy import graph, ops -from weave.legacy import weave_types as types +from weave.legacy.weave import graph, ops, weave_internal +from weave.legacy.weave import weave_types as types -from ... import weave_internal -from ...legacy import weavify -from .. import geom +from ...legacy.weave import weavify +from weave.legacy.tests.util import geom @pytest.mark.parametrize( diff --git a/weave/tests/legacy/test_with_columns.py b/weave/legacy/tests/test_with_columns.py similarity index 98% rename from weave/tests/legacy/test_with_columns.py rename to weave/legacy/tests/test_with_columns.py index 1d7404471d1..e68b8e10435 100644 --- a/weave/tests/legacy/test_with_columns.py +++ b/weave/legacy/tests/test_with_columns.py @@ -1,5 +1,5 @@ import weave -from weave.legacy import ops_arrow +from weave.legacy.weave import ops_arrow def test_with_columns_basic(): diff --git a/weave/legacy/language_features/__init__.py b/weave/legacy/tests/util/__init__.py similarity index 100% rename from weave/legacy/language_features/__init__.py rename to weave/legacy/tests/util/__init__.py diff --git a/weave/tests/concrete_tagged_value.py b/weave/legacy/tests/util/concrete_tagged_value.py similarity index 94% rename from weave/tests/concrete_tagged_value.py rename to weave/legacy/tests/util/concrete_tagged_value.py index ff7d60442a0..403ab8fa21f 100644 --- a/weave/tests/concrete_tagged_value.py +++ b/weave/legacy/tests/util/concrete_tagged_value.py @@ -5,8 +5,8 @@ import dataclasses import typing -from weave.legacy import box -from weave.legacy.language_features.tagging import tag_store +from weave.legacy.weave import box +from weave.legacy.weave.language_features.tagging import tag_store @dataclasses.dataclass diff --git a/weave/tests/geom.py b/weave/legacy/tests/util/geom.py similarity index 91% rename from weave/tests/geom.py rename to weave/legacy/tests/util/geom.py index 93653728cc5..46cbdb7e44e 100644 --- a/weave/tests/geom.py +++ b/weave/legacy/tests/util/geom.py @@ -3,8 +3,8 @@ import typing import weave -from weave.legacy import context_state as _context_state -from weave.legacy import panels +from weave.legacy.weave import context_state as _context_state +from weave.legacy.weave import panels _loading_builtins_token = _context_state.set_loading_built_ins() diff --git a/weave/tests/list_arrow_test_helpers.py b/weave/legacy/tests/util/list_arrow_test_helpers.py similarity index 82% rename from weave/tests/list_arrow_test_helpers.py rename to weave/legacy/tests/util/list_arrow_test_helpers.py index 8c2fedcd4a9..c130ba5cde0 100644 --- a/weave/tests/list_arrow_test_helpers.py +++ b/weave/legacy/tests/util/list_arrow_test_helpers.py @@ -1,7 +1,6 @@ -from weave.legacy import ops_arrow as arrow -from weave.legacy.ops_primitives import list_ - -from .. import api as weave +from weave.legacy.weave import api as weave +from weave.legacy.weave import ops_arrow as arrow +from weave.legacy.weave.ops_primitives import list_ class ListLikeNodeInterface: diff --git a/weave/tests/op_specs.py b/weave/legacy/tests/util/op_specs.py similarity index 98% rename from weave/tests/op_specs.py rename to weave/legacy/tests/util/op_specs.py index cf10582a1b8..9924f60754b 100644 --- a/weave/tests/op_specs.py +++ b/weave/legacy/tests/util/op_specs.py @@ -3,13 +3,11 @@ import typing import weave -from weave.legacy import op_def, ops_primitives -from weave.legacy.language_features.tagging import tagged_value_type -from weave.legacy.timestamp import PY_DATETIME_MAX_MS, PY_DATETIME_MIN_MS +from weave.legacy.weave import op_def, ops_primitives +from weave.legacy.weave.language_features.tagging import tagged_value_type +from weave.legacy.weave.timestamp import PY_DATETIME_MAX_MS, PY_DATETIME_MIN_MS -from .concrete_tagged_value import ( - TaggedValue, -) +from .concrete_tagged_value import TaggedValue @dataclasses.dataclass diff --git a/weave/tests/tag_test_util.py b/weave/legacy/tests/util/tag_test_util.py similarity index 55% rename from weave/tests/tag_test_util.py rename to weave/legacy/tests/util/tag_test_util.py index a444466c413..bfed6da0b88 100644 --- a/weave/tests/tag_test_util.py +++ b/weave/legacy/tests/util/tag_test_util.py @@ -1,14 +1,14 @@ import weave -from weave.legacy import box, graph -from weave.legacy import context_state as _context_state -from weave.legacy import weave_types as types -from weave.legacy.language_features.tagging import tag_store -from weave.legacy.language_features.tagging.tagged_value_type import TaggedValueType +from weave.legacy.weave import box, graph +from weave.legacy.weave import context_state as _context_state +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.language_features.tagging import tag_store +from weave.legacy.weave.language_features.tagging.tagged_value_type import TaggedValueType tag_adders = 0 -def op_add_tag(obj_node: graph.Node, tags: dict[str, str]): +def op_add_tag(obj_node: graph.Node, tags: dict[str, str]): # type: ignore[no-untyped-def] global tag_adders tag_adders += 1 name = f"_custom_tagger_{tag_adders}" @@ -23,7 +23,7 @@ def op_add_tag(obj_node: graph.Node, tags: dict[str, str]): input_types["obj"], ), ) - def custom_tagger(obj): + def custom_tagger(obj): # type: ignore[no-untyped-def] return tag_store.add_tags( box.box(obj), {f"_ct_{k}": v for k, v in tags.items()} ) @@ -33,7 +33,7 @@ def custom_tagger(obj): return custom_tagger(obj_node) -def make_get_tag(tag_name: str): - from weave.legacy.language_features.tagging import make_tag_getter_op +def make_get_tag(tag_name: str): # type: ignore[no-untyped-def] + from weave.legacy.weave.language_features.tagging import make_tag_getter_op return make_tag_getter_op.make_tag_getter_op(f"_ct_{tag_name}", types.String()) diff --git a/weave/tests/weavejs_ops.py b/weave/legacy/tests/util/weavejs_ops.py similarity index 91% rename from weave/tests/weavejs_ops.py rename to weave/legacy/tests/util/weavejs_ops.py index 2d4764e3f58..afaab26438b 100644 --- a/weave/tests/weavejs_ops.py +++ b/weave/legacy/tests/util/weavejs_ops.py @@ -1,16 +1,14 @@ # WeaveJS ops used for testing. These are not used in production. -from weave.legacy import graph -from weave.legacy import weave_types as types -from weave.legacy._dict_utils import typeddict_pick_output_type -from weave.legacy.language_features.tagging import tagged_value_type -from weave.legacy.language_features.tagging.tagging_op_logic import ( +from weave.legacy.weave import graph, weave_internal +from weave.legacy.weave import weave_types as types +from weave.legacy.weave._dict_utils import typeddict_pick_output_type +from weave.legacy.weave.language_features.tagging import tagged_value_type +from weave.legacy.weave.language_features.tagging.tagging_op_logic import ( op_get_tag_type_resolver, op_make_type_tagged_resolver, ) -from .. import weave_internal - def ensure_node(v): if isinstance(v, graph.Node): diff --git a/weave/legacy/language_features/tagging/__init__.py b/weave/legacy/weave/__init__.py similarity index 100% rename from weave/legacy/language_features/tagging/__init__.py rename to weave/legacy/weave/__init__.py diff --git a/weave/legacy/_dict_utils.py b/weave/legacy/weave/_dict_utils.py similarity index 97% rename from weave/legacy/_dict_utils.py rename to weave/legacy/weave/_dict_utils.py index 744b62474ff..4636d46037f 100644 --- a/weave/legacy/_dict_utils.py +++ b/weave/legacy/weave/_dict_utils.py @@ -1,10 +1,10 @@ import typing -from weave.legacy import box -from weave.legacy.language_features.tagging import tag_store, tagged_value_type +from weave.legacy.weave import box +from weave.legacy.weave.language_features.tagging import tag_store, tagged_value_type -from weave.legacy import errors -from weave.legacy import weave_types as types +from weave.legacy.weave import errors +from weave.legacy.weave import weave_types as types def typeddict_pick_output_type(input_types): diff --git a/weave/legacy/api.py b/weave/legacy/weave/api.py similarity index 78% rename from weave/legacy/api.py rename to weave/legacy/weave/api.py index c310b40db08..13d34e60760 100644 --- a/weave/legacy/api.py +++ b/weave/legacy/weave/api.py @@ -2,50 +2,50 @@ import typing -from weave.legacy import graph as _graph -from weave.legacy.graph import Node +from weave.legacy.weave import graph as _graph +from weave.legacy.weave.graph import Node # If this is not imported, serialization of Weave Nodes is incorrect! -from weave.legacy import graph_mapper as _graph_mapper +from weave.legacy.weave import graph_mapper as _graph_mapper -from .. import storage as _storage +from . import storage as _storage from . import ref_base as _ref_base -from weave.legacy import wandb_api as _wandb_api +from weave.legacy.weave import wandb_api as _wandb_api -from .. import weave_internal as _weave_internal +from weave.legacy.weave import weave_internal as _weave_internal -from weave.legacy import util as _util +from weave.legacy.weave import util as _util -from weave.legacy import context as _context -from .. import weave_init as _weave_init -from ..trace import weave_client as _weave_client +from weave.legacy.weave import context as _context +from ...trace import weave_init as _weave_init +from ...trace import weave_client as _weave_client # exposed as part of api -from weave.legacy import weave_types as types +from weave.legacy.weave import weave_types as types # needed to enable automatic numpy serialization from . import types_numpy as _types_numpy -from weave.legacy import errors -from weave.legacy.decorators import weave_class, mutation, type +from weave.legacy.weave import errors +from weave.legacy.weave.decorators import weave_class, mutation, type -from weave.legacy import usage_analytics -from weave.legacy.context import ( +from weave.legacy.weave import usage_analytics +from weave.legacy.weave.context import ( use_fixed_server_port, use_frontend_devmode, # eager_execution, use_lazy_execution, ) -from weave.legacy.panel import Panel +from weave.legacy.weave.panel import Panel -from weave.legacy.arrow.list_ import ArrowWeaveList as WeaveList +from weave.legacy.weave.arrow.list_ import ArrowWeaveList as WeaveList # TODO: This is here because the op overloaded... from weave.trace.op import op # noqa: F401 def save(node_or_obj, name=None): # type: ignore - from weave.legacy.ops_primitives.weave_api import get, save + from weave.legacy.weave.ops_primitives.weave_api import get, save if isinstance(node_or_obj, _graph.Node): return save(node_or_obj, name=name) diff --git a/weave/legacy/ops_domain/run_history/__init__.py b/weave/legacy/weave/arrow/__init__.py similarity index 100% rename from weave/legacy/ops_domain/run_history/__init__.py rename to weave/legacy/weave/arrow/__init__.py diff --git a/weave/legacy/arrow/arrow.py b/weave/legacy/weave/arrow/arrow.py similarity index 97% rename from weave/legacy/arrow/arrow.py rename to weave/legacy/weave/arrow/arrow.py index 0c5576eaa99..b6cc10e8790 100644 --- a/weave/legacy/arrow/arrow.py +++ b/weave/legacy/weave/arrow/arrow.py @@ -11,9 +11,9 @@ py_type = type -from weave.legacy import errors -from weave.legacy import weave_types as types -from weave.legacy import artifact_fs, partial_object +from weave.legacy.weave import errors +from weave.legacy.weave import weave_types as types +from weave.legacy.weave import artifact_fs, partial_object def arrow_type_to_weave_type(pa_type: pa.DataType) -> types.Type: @@ -184,7 +184,7 @@ def save_instance(self, obj, artifact, name): ) # v1 AWL format - # from weave.legacy.arrow import convert + # from weave.legacy.weave.arrow import convert # parquet_friendly = convert.to_parquet_friendly(obj) # table = pa.table({"arr": parquet_friendly._arrow_data}) @@ -223,7 +223,7 @@ def load_instance( with artifact.open(f"{name}.ArrowWeaveList.type.json") as f: object_type = json.load(f) object_type = types.TypeRegistry.type_from_dict(object_type) - from weave.legacy.arrow import list_ + from weave.legacy.weave.arrow import list_ if "_weave_awl_format" not in artifact.metadata: # v1 AWL format @@ -232,7 +232,7 @@ def load_instance( arr = table["arr"].combine_chunks() with list_.unsafe_awl_construction("load_from_parquet"): l = self.instance_class(arr, object_type=object_type, artifact=artifact) # type: ignore - from weave.legacy.arrow import convert + from weave.legacy.weave.arrow import convert res = convert.from_parquet_friendly(l) elif artifact.metadata["_weave_awl_format"] == 2: @@ -366,7 +366,7 @@ def rewrite_weavelist_refs(arrow_data, object_type, source_artifact, target_arti def _object_type_has_props(object_type): - from weave.legacy.language_features.tagging import tagged_value_type + from weave.legacy.weave.language_features.tagging import tagged_value_type return ( isinstance(object_type, types.TypedDict) @@ -376,7 +376,7 @@ def _object_type_has_props(object_type): def _object_type_prop_types(object_type): - from weave.legacy.language_features.tagging import tagged_value_type + from weave.legacy.weave.language_features.tagging import tagged_value_type if isinstance(object_type, tagged_value_type.TaggedValueType): return { diff --git a/weave/legacy/arrow/arrow_tags.py b/weave/legacy/weave/arrow/arrow_tags.py similarity index 93% rename from weave/legacy/arrow/arrow_tags.py rename to weave/legacy/weave/arrow/arrow_tags.py index 52ba674abca..3fb27314a00 100644 --- a/weave/legacy/arrow/arrow_tags.py +++ b/weave/legacy/weave/arrow/arrow_tags.py @@ -3,10 +3,10 @@ import pyarrow as pa from pyarrow import compute as pc -from weave.legacy import weave_types as types -from weave.legacy.arrow import convert -from weave.legacy.arrow.arrow import offsets_starting_at_zero -from weave.legacy.language_features.tagging import ( +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.arrow import convert +from weave.legacy.weave.arrow.arrow import offsets_starting_at_zero +from weave.legacy.weave.language_features.tagging import ( process_opdef_output_type, tag_store, ) @@ -109,7 +109,7 @@ def awl_add_arrow_tags( new_object_type = process_opdef_output_type.op_make_type_tagged_resolver( l.object_type, tag_type ) - from weave.legacy.arrow.list_ import ArrowWeaveList + from weave.legacy.weave.arrow.list_ import ArrowWeaveList res: ArrowWeaveList = ArrowWeaveList(new_value, new_object_type, l._artifact) if tag_store.is_tagged(l): diff --git a/weave/legacy/arrow/concat.py b/weave/legacy/weave/arrow/concat.py similarity index 98% rename from weave/legacy/arrow/concat.py rename to weave/legacy/weave/arrow/concat.py index 46453d7de70..d383a9c09c9 100644 --- a/weave/legacy/arrow/concat.py +++ b/weave/legacy/weave/arrow/concat.py @@ -12,9 +12,9 @@ import pyarrow as pa from pyarrow import compute as pc -from weave.legacy import errors -from weave.legacy import weave_types as types -from weave.legacy.arrow.list_ import ( +from weave.legacy.weave import errors +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.arrow.list_ import ( ArrowWeaveList, ArrowWeaveListGeneric, is_list_arrowweavelist, @@ -26,11 +26,11 @@ offsets_starting_at_zero, unsafe_awl_construction, ) -from weave.legacy.language_features.tagging import tagged_value_type +from weave.legacy.weave.language_features.tagging import tagged_value_type DEBUG = False -from weave.legacy import engine_trace +from weave.legacy.weave import engine_trace tracer = engine_trace.tracer() diff --git a/weave/legacy/arrow/constructors.py b/weave/legacy/weave/arrow/constructors.py similarity index 90% rename from weave/legacy/arrow/constructors.py rename to weave/legacy/weave/arrow/constructors.py index 578643160c8..6beeb475d92 100644 --- a/weave/legacy/arrow/constructors.py +++ b/weave/legacy/weave/arrow/constructors.py @@ -3,13 +3,13 @@ import pyarrow as pa -from weave.legacy import errors -from weave.legacy import weave_types as types -from weave.legacy import artifact_base, box -from weave.legacy.arrow import arrow_tags, convert -from weave.legacy.arrow.arrow import ArrowWeaveListType, arrow_as_array -from weave.legacy.arrow.list_ import ArrowWeaveList -from weave.legacy.language_features.tagging import tag_store, tagged_value_type +from weave.legacy.weave import errors +from weave.legacy.weave import weave_types as types +from weave.legacy.weave import artifact_base, box +from weave.legacy.weave.arrow import arrow_tags, convert +from weave.legacy.weave.arrow.arrow import ArrowWeaveListType, arrow_as_array +from weave.legacy.weave.arrow.list_ import ArrowWeaveList +from weave.legacy.weave.language_features.tagging import tag_store, tagged_value_type def repeat(value: typing.Any, count: int) -> pa.Array: diff --git a/weave/legacy/arrow/convert.py b/weave/legacy/weave/arrow/convert.py similarity index 98% rename from weave/legacy/arrow/convert.py rename to weave/legacy/weave/arrow/convert.py index d76edeaef7b..a53a610fd01 100644 --- a/weave/legacy/arrow/convert.py +++ b/weave/legacy/weave/arrow/convert.py @@ -3,19 +3,19 @@ import pyarrow as pa import pyarrow.compute as pc -from weave.legacy import api as api -from weave import weave_internal -from weave.legacy import weave_types as types -from weave.legacy import errors, arrow_util, artifact_base, artifact_mem, box, mappers_arrow -from weave.legacy.arrow.arrow import ( +from weave.legacy.weave import api as api +from weave.legacy.weave import weave_internal +from weave.legacy.weave import weave_types as types +from weave.legacy.weave import errors, arrow_util, artifact_base, artifact_mem, box, mappers_arrow +from weave.legacy.weave.arrow.arrow import ( ArrowWeaveListType, ) -from weave.legacy.arrow.list_ import ( +from weave.legacy.weave.arrow.list_ import ( ArrowWeaveList, PathType, unsafe_awl_construction, ) -from weave.legacy.language_features.tagging import tag_store, tagged_value_type +from weave.legacy.weave.language_features.tagging import tag_store, tagged_value_type # Hmm... this doesn't work on ObjectType, which contains a Union of Struct... # We need that because our ImageFileArtifactRefType has a union of structs @@ -502,7 +502,7 @@ def simple_to_string(arr: pa.Array): def to_compare_safe(awl: ArrowWeaveList) -> ArrowWeaveList: """Converts any ArrowWeaveList to simple type that pa.compute.equal can compare.""" - from weave.legacy.ops_domain.wbmedia import ArtifactAssetType + from weave.legacy.weave.ops_domain.wbmedia import ArtifactAssetType # Returns a number of string arrow weave list, possibly with Nones def _to_compare_safe( diff --git a/weave/legacy/arrow/list_.py b/weave/legacy/weave/arrow/list_.py similarity index 98% rename from weave/legacy/arrow/list_.py rename to weave/legacy/weave/arrow/list_.py index e90c860fead..6e3d0e85161 100644 --- a/weave/legacy/arrow/list_.py +++ b/weave/legacy/weave/arrow/list_.py @@ -10,11 +10,9 @@ import pyarrow.compute as pc import typing_extensions -from weave import ( +from weave.legacy.weave import weave_types as types +from weave.legacy.weave import ( weave_internal, -) -from weave.legacy import weave_types as types -from weave.legacy import ( errors, _dict_utils, artifact_base, @@ -27,7 +25,7 @@ node_ref, ref_base, ) -from weave.legacy.arrow.arrow import ( +from weave.legacy.weave.arrow.arrow import ( ArrowWeaveListType, arrow_as_array, arrow_zip, @@ -35,7 +33,7 @@ pretty_print_arrow_type, safe_is_null, ) -from weave.legacy.language_features.tagging import ( +from weave.legacy.weave.language_features.tagging import ( tag_store, tagged_value_type, ) @@ -496,7 +494,7 @@ def unsafe_awl_construction(reason: str): def dict_of_columns_to_awl(d: dict[str, typing.Any]) -> "ArrowWeaveList": - from weave.legacy.arrow import constructors + from weave.legacy.weave.arrow import constructors cols = {} for k, v in d.items(): @@ -524,7 +522,7 @@ def __init__( artifact: typing.Optional[artifact_base.Artifact] = None, invalid_reason=None, ) -> None: - from weave.legacy.arrow import constructors, convert + from weave.legacy.weave.arrow import constructors, convert # Do not dictionary decode this array! That will break performance. # Note we combine chunks here, to make the internal interface easy @@ -612,7 +610,7 @@ def __add__(self, other) -> "ArrowWeaveList": if not isinstance(other, ArrowWeaveList): raise TypeError(f"Expected list or ArrowWeaveList, got {type(other)}") - from weave.legacy.arrow import concat + from weave.legacy.weave.arrow import concat return concat.concatenate(self, other) @@ -903,7 +901,7 @@ def _map_column( type_codes, pa.scalar(i, pa.int8()), ) - from weave.legacy.arrow import concat + from weave.legacy.weave.arrow import concat members[i] = concat.concatenate(member_i, member_j) members[j] = None @@ -1501,12 +1499,12 @@ def apply( self, fn: typing.Union[typing.Callable[[typing.Any], typing.Any], graph.Node] ): fn = self._make_lambda_node(fn) - from weave.legacy.ops_arrow.vectorize import _apply_fn_node_with_tag_pushdown + from weave.legacy.weave.ops_arrow.vectorize import _apply_fn_node_with_tag_pushdown return _apply_fn_node_with_tag_pushdown(self, fn) # type: ignore def concat(self, other: "ArrowWeaveList") -> "ArrowWeaveList": - from weave.legacy.arrow import concat + from weave.legacy.weave.arrow import concat return concat.concatenate(self, other) @@ -1520,7 +1518,7 @@ def join2( leftOuter: bool = False, rightOuter: bool = False, ): - from weave.legacy.ops_arrow import list_join + from weave.legacy.weave.ops_arrow import list_join join1Fn = self._make_lambda_node(join1Fn) join2Fn = other._make_lambda_node(join2Fn) @@ -1668,7 +1666,7 @@ def make_vec_taggedvalue( def awl_zip(*arrs: ArrowWeaveList) -> ArrowWeaveList: if not arrs: raise ValueError("Cannot zip empty list") - from weave.legacy.arrow import convert + from weave.legacy.weave.arrow import convert arrs = convert.unify_types(*arrs) zipped = arrow_zip(*[a._arrow_data for a in arrs]) diff --git a/weave/legacy/arrow_util.py b/weave/legacy/weave/arrow_util.py similarity index 100% rename from weave/legacy/arrow_util.py rename to weave/legacy/weave/arrow_util.py diff --git a/weave/legacy/artifact_base.py b/weave/legacy/weave/artifact_base.py similarity index 95% rename from weave/legacy/artifact_base.py rename to weave/legacy/weave/artifact_base.py index ae98ed7bc84..3d1b56de86d 100644 --- a/weave/legacy/artifact_base.py +++ b/weave/legacy/weave/artifact_base.py @@ -7,8 +7,8 @@ import typing -from weave.legacy import weave_types as types -from weave.legacy import ref_base +from weave.legacy.weave import weave_types as types +from weave.legacy.weave import ref_base class Artifact: diff --git a/weave/legacy/artifact_fs.py b/weave/legacy/weave/artifact_fs.py similarity index 97% rename from weave/legacy/artifact_fs.py rename to weave/legacy/weave/artifact_fs.py index 36ab31229a8..37cc0a3b5ae 100644 --- a/weave/legacy/artifact_fs.py +++ b/weave/legacy/weave/artifact_fs.py @@ -6,14 +6,14 @@ import os import typing -from weave.legacy import errors -from weave.legacy import weave_types as types -from weave.legacy import artifact_base, file_base, object_context, ref_base, uris -from weave.legacy.language_features.tagging import tag_store +from weave.legacy.weave import errors +from weave.legacy.weave import weave_types as types +from weave.legacy.weave import artifact_base, file_base, object_context, ref_base, uris +from weave.legacy.weave.language_features.tagging import tag_store from weave.trace import ref_util if typing.TYPE_CHECKING: - from weave.legacy import graph + from weave.legacy.weave import graph class FilesystemArtifactType(types.Type): @@ -85,7 +85,7 @@ def get(self, key: str, type_: types.Type) -> typing.Any: return self.ref_from_local_str(key, type_).get() def as_node(self) -> "graph.Node": - from weave.legacy.ops_primitives.weave_api import get as op_get + from weave.legacy.weave.ops_primitives.weave_api import get as op_get return op_get(str(self)) @@ -267,7 +267,7 @@ def type(self) -> types.Type: ot = self._outer_type if self.extra is not None: - from weave.legacy import types_numpy + from weave.legacy.weave import types_numpy if not types.is_list_like(ot) and isinstance( ot, types_numpy.NumpyArrayType @@ -451,7 +451,7 @@ def size(self) -> int: return self.artifact.size(self.path) def digest(self) -> typing.Optional[str]: - from weave.legacy.artifact_wandb import WandbArtifact + from weave.legacy.weave.artifact_wandb import WandbArtifact if isinstance(self.artifact, WandbArtifact): # we can get the digest from the manifest (much faster) diff --git a/weave/legacy/artifact_local.py b/weave/legacy/weave/artifact_local.py similarity index 99% rename from weave/legacy/artifact_local.py rename to weave/legacy/weave/artifact_local.py index a22b6ea30e9..fc49c4c187e 100644 --- a/weave/legacy/artifact_local.py +++ b/weave/legacy/weave/artifact_local.py @@ -9,9 +9,9 @@ import typing from datetime import datetime -from weave.legacy import filesystem -from weave.legacy import weave_types as types -from weave.legacy import ( +from weave.legacy.weave import filesystem +from weave.legacy.weave import weave_types as types +from weave.legacy.weave import ( artifact_fs, artifact_wandb, environment, diff --git a/weave/legacy/artifact_mem.py b/weave/legacy/weave/artifact_mem.py similarity index 90% rename from weave/legacy/artifact_mem.py rename to weave/legacy/weave/artifact_mem.py index 3e01f70ccb3..44fb3f9ff0e 100644 --- a/weave/legacy/artifact_mem.py +++ b/weave/legacy/weave/artifact_mem.py @@ -1,9 +1,9 @@ import typing from . import artifact_base -from weave.legacy import weave_types as types -from weave.legacy import errors -from weave.legacy import ref_base +from weave.legacy.weave import weave_types as types +from weave.legacy.weave import errors +from weave.legacy.weave import ref_base class MemArtifact(artifact_base.Artifact): diff --git a/weave/legacy/artifact_wandb.py b/weave/legacy/weave/artifact_wandb.py similarity index 99% rename from weave/legacy/artifact_wandb.py rename to weave/legacy/weave/artifact_wandb.py index eff48f0519d..a19a33af7ef 100644 --- a/weave/legacy/artifact_wandb.py +++ b/weave/legacy/weave/artifact_wandb.py @@ -13,8 +13,8 @@ from wandb.apis.public import api as wb_public from wandb.sdk.lib.hashutil import b64_to_hex_id, hex_to_b64_id -from weave.legacy import weave_types as types -from weave.legacy import ( +from weave.legacy.weave import weave_types as types +from weave.legacy.weave import ( filesystem, urls, errors, @@ -28,10 +28,10 @@ wandb_client_api, util, ) -from weave.legacy.wandb_interface import wandb_artifact_pusher +from weave.legacy.weave.wandb_interface import wandb_artifact_pusher if typing.TYPE_CHECKING: - from weave.legacy.wandb_interface.wandb_lite_run import InMemoryLazyLiteRun + from weave.legacy.weave.wandb_interface.wandb_lite_run import InMemoryLazyLiteRun from .run_streamtable_span import RunStreamTableSpan @@ -417,7 +417,7 @@ def __init__( ] ] = None, ): - from weave.legacy import io_service + from weave.legacy.weave import io_service self.io_service = io_service.get_sync_client() self.name = name @@ -857,7 +857,7 @@ def from_uri(cls, uri: uris.WeaveURI) -> "WandbArtifactRef": @property def ui_url(self): root_type = self.type.root_type_class() - from weave.legacy.op_def_type import OpDefType + from weave.legacy.weave.op_def_type import OpDefType if issubclass(root_type, OpDefType): return urls.op_version_path( diff --git a/weave/legacy/async_demo.py b/weave/legacy/weave/async_demo.py similarity index 97% rename from weave/legacy/async_demo.py rename to weave/legacy/weave/async_demo.py index 9dcd34bd5f3..1e1f7a3d28a 100644 --- a/weave/legacy/async_demo.py +++ b/weave/legacy/weave/async_demo.py @@ -3,7 +3,7 @@ import time import weave -from weave.legacy import context_state as _context_state +from weave.legacy.weave import context_state as _context_state _loading_builtins_token = _context_state.set_loading_built_ins() diff --git a/weave/legacy/async_map.py b/weave/legacy/weave/async_map.py similarity index 100% rename from weave/legacy/async_map.py rename to weave/legacy/weave/async_map.py diff --git a/weave/legacy/async_queue.py b/weave/legacy/weave/async_queue.py similarity index 100% rename from weave/legacy/async_queue.py rename to weave/legacy/weave/async_queue.py diff --git a/weave/legacy/box.py b/weave/legacy/weave/box.py similarity index 99% rename from weave/legacy/box.py rename to weave/legacy/weave/box.py index b957fc6ba3f..a16725af94a 100644 --- a/weave/legacy/box.py +++ b/weave/legacy/weave/box.py @@ -5,7 +5,7 @@ import numpy as np from weave.trace import ref_util -from weave.legacy import context_state +from weave.legacy.weave import context_state def make_id() -> int: diff --git a/weave/legacy/cache.py b/weave/legacy/weave/cache.py similarity index 98% rename from weave/legacy/cache.py rename to weave/legacy/weave/cache.py index bb254ca9473..5db00184bfa 100644 --- a/weave/legacy/cache.py +++ b/weave/legacy/weave/cache.py @@ -6,8 +6,8 @@ import time import typing -from weave.legacy import errors -from weave.legacy import context_state, environment, wandb_api, engine_trace +from weave.legacy.weave import errors +from weave.legacy.weave import context_state, environment, wandb_api, engine_trace statsd = engine_trace.statsd() # type: ignore logger = logging.getLogger("root") diff --git a/weave/legacy/client.py b/weave/legacy/weave/client.py similarity index 91% rename from weave/legacy/client.py rename to weave/legacy/weave/client.py index e1a3a32ce0c..9c10cd2f455 100644 --- a/weave/legacy/client.py +++ b/weave/legacy/weave/client.py @@ -1,5 +1,5 @@ -from weave.legacy import weave_types -from weave.legacy import storage +from weave.legacy.weave import weave_types +from weave.legacy.weave import storage class Client: diff --git a/weave/legacy/client_interface.py b/weave/legacy/weave/client_interface.py similarity index 100% rename from weave/legacy/client_interface.py rename to weave/legacy/weave/client_interface.py diff --git a/weave/legacy/codifiable_value_mixin.py b/weave/legacy/weave/codifiable_value_mixin.py similarity index 100% rename from weave/legacy/codifiable_value_mixin.py rename to weave/legacy/weave/codifiable_value_mixin.py diff --git a/weave/legacy/codify.py b/weave/legacy/weave/codify.py similarity index 91% rename from weave/legacy/codify.py rename to weave/legacy/weave/codify.py index e1017261d77..0e6b5c273b6 100644 --- a/weave/legacy/codify.py +++ b/weave/legacy/weave/codify.py @@ -6,8 +6,8 @@ import black -from weave.legacy import weave_types -from weave.legacy import storage, graph, registry_mem +from weave.legacy.weave import weave_types +from weave.legacy.weave import storage, graph, registry_mem from . import codifiable_value_mixin @@ -97,7 +97,7 @@ def _try_otc_using_codifiable_mixin(obj: typing.Any) -> typing.Optional[str]: # assert weave_types.type_name_to_type(obj_type.name)() == obj_type # d = obj_type.instance_to_dict(obj) # obj_type_name = obj_type.name -# return f"""weave.legacy.codify.load_type({obj_type_name}, {d})""" +# return f"""weave.legacy.weave.codify.load_type({obj_type_name}, {d})""" # return None @@ -128,7 +128,7 @@ def _try_otc_using_dataclasses(obj: typing.Any) -> typing.Optional[str]: if class_type.__module__.startswith("weave.decorator_type") and issubclass( class_type, weave_types.Type ): - qualified_classpath = "weave.legacy.weave_types" + qualified_classpath = "weave.legacy.weave.weave_types" qualified_classname = f"type_name_to_type('{class_type.name}')" else: qualified_classpath = _module_name_corrections(class_type.__module__) @@ -162,7 +162,7 @@ def _try_otc_using_dataclasses(obj: typing.Any) -> typing.Optional[str]: def _otc_using_storage_fallback(obj: typing.Any) -> str: - return f"""weave.legacy.codify.load({storage.to_python(obj)})""" + return f"""weave.legacy.weave.codify.load({storage.to_python(obj)})""" # Helpers @@ -170,8 +170,8 @@ def _otc_using_storage_fallback(obj: typing.Any) -> str: # Hack: def _module_name_corrections(qualified_name: str) -> str: - if qualified_name == "weave.legacy.ops_primitives.file_local": - return "weave.legacy.ops" + if qualified_name == "weave.legacy.weave.ops_primitives.file_local": + return "weave.legacy.weave.ops" elif qualified_name.startswith("weave.decorator_class"): raise ValueError("Decorator classes are not supported.") elif qualified_name.startswith("weave.decorator_type"): @@ -201,7 +201,7 @@ def _node_to_code(node: graph.Node, wrap_const_node: bool = True) -> str: if current_frame is not None and node.name in current_frame: return node.name - return f"weave.weave_internal.make_var_node({_type_to_code(node.type)}, '{node.name}')" + return f"weave.legacy.weave.weave_internal.make_var_node({_type_to_code(node.type)}, '{node.name}')" elif isinstance(node, graph.ConstNode): if isinstance(node.type, weave_types.Function): vars = list(node.type.input_types.keys()) @@ -209,7 +209,7 @@ def _node_to_code(node: graph.Node, wrap_const_node: bool = True) -> str: else: val_as_code = object_to_code_no_format(node.val) if wrap_const_node: - return f"weave.weave_internal.const({val_as_code})" + return f"weave.legacy.weave.weave_internal.const({val_as_code})" else: return val_as_code elif isinstance(node, graph.OutputNode): @@ -226,7 +226,7 @@ def _node_to_code(node: graph.Node, wrap_const_node: bool = True) -> str: ) if len(node.from_op.inputs) > 0: args += "," - return f"weave.legacy.ops_primitives.dict.dict_({args})" + return f"weave.legacy.weave.ops_primitives.dict.dict_({args})" elif node.from_op.name == "list": args = ",".join( [ @@ -236,7 +236,7 @@ def _node_to_code(node: graph.Node, wrap_const_node: bool = True) -> str: ) if len(node.from_op.inputs) > 0: args += "," - return f"weave.legacy.ops_primitives.list_.make_list({args})" + return f"weave.legacy.weave.ops_primitives.list_.make_list({args})" is_root = len(inputs) == 0 or not isinstance( inputs[0], (graph.OutputNode, graph.VarNode) diff --git a/weave/legacy/compile.py b/weave/legacy/weave/compile.py similarity index 99% rename from weave/legacy/compile.py rename to weave/legacy/weave/compile.py index 1e307054bd0..318e980ee39 100644 --- a/weave/legacy/compile.py +++ b/weave/legacy/weave/compile.py @@ -5,11 +5,9 @@ import re import typing -from weave import ( +from weave.legacy.weave import weave_types as types +from weave.legacy.weave import ( weave_internal, -) -from weave.legacy import weave_types as types -from weave.legacy import ( errors, engine_trace, box, @@ -30,8 +28,8 @@ stitch, value_or_error, ) -from weave.legacy.language_features.tagging import tagged_value_type_helpers -from weave.legacy.op_def import OpDef +from weave.legacy.weave.language_features.tagging import tagged_value_type_helpers +from weave.legacy.weave.op_def import OpDef # These call_* functions must match the actual op implementations. # But we don't want to import the op definitions themselves here, since @@ -43,7 +41,7 @@ def _dispatch_error_is_client_error( op_name: str, input_types: dict[str, types.Type] ) -> bool: - from weave.legacy.ops_domain import wbmedia + from weave.legacy.weave.ops_domain import wbmedia if op_name in set( ( @@ -270,7 +268,7 @@ def _simple_optimizations(node: graph.Node) -> typing.Optional[graph.Node]: node.type, "ArrowWeaveListTypedDict-columnNames", {"self": awl_node} ) elif isinstance(node, graph.OutputNode) and node.from_op.name == "flatten": - from weave.legacy.arrow.arrow import ArrowWeaveListType + from weave.legacy.weave.arrow.arrow import ArrowWeaveListType from .ops_arrow.list_ops import _concat_output_type @@ -286,7 +284,7 @@ def _simple_optimizations(node: graph.Node) -> typing.Optional[graph.Node]: {"arr": arr_node}, ) elif isinstance(node, graph.OutputNode) and node.from_op.name == "concat": - from weave.legacy.arrow.arrow import ArrowWeaveListType + from weave.legacy.weave.arrow.arrow import ArrowWeaveListType from .ops_arrow.list_ops import flatten_return_type @@ -489,7 +487,7 @@ def compile_dedupe( nodes: dict[str, graph.Node] = {} def _dedupe(node: graph.Node) -> graph.Node: - from weave.legacy import serialize + from weave.legacy.weave import serialize node_id = serialize.node_id(node) if node_id in nodes: diff --git a/weave/legacy/compile_domain.py b/weave/legacy/weave/compile_domain.py similarity index 97% rename from weave/legacy/compile_domain.py rename to weave/legacy/weave/compile_domain.py index 65730e05ab3..fa002b254c3 100644 --- a/weave/legacy/compile_domain.py +++ b/weave/legacy/weave/compile_domain.py @@ -2,13 +2,13 @@ import graphql -from weave.legacy import errors -from weave.legacy import weave_types as types -from weave.legacy import gql_op_plugin, gql_to_weave, graph, op_args, stitch, registry_mem -from weave.legacy.input_provider import InputAndStitchProvider +from weave.legacy.weave import errors +from weave.legacy.weave import weave_types as types +from weave.legacy.weave import gql_op_plugin, gql_to_weave, graph, op_args, stitch, registry_mem +from weave.legacy.weave.input_provider import InputAndStitchProvider if typing.TYPE_CHECKING: - from weave.legacy import op_def + from weave.legacy.weave import op_def def fragment_to_query(fragment: str) -> str: diff --git a/weave/legacy/compile_table.py b/weave/legacy/weave/compile_table.py similarity index 92% rename from weave/legacy/compile_table.py rename to weave/legacy/weave/compile_table.py index f0efcfc85ec..bbf9f868b49 100644 --- a/weave/legacy/compile_table.py +++ b/weave/legacy/weave/compile_table.py @@ -2,8 +2,8 @@ import typing -from weave.legacy import errors -from weave.legacy import stitch +from weave.legacy.weave import errors +from weave.legacy.weave import stitch KeyTree = typing.Dict[str, "KeyTree"] # type:ignore @@ -20,7 +20,7 @@ def tree_merge(a: KeyTree, b: KeyTree) -> None: def get_projection(obj: stitch.ObjectRecorder) -> KeyTree: """Given an object returned by stitch, return a tree of all accessed columns.""" - from weave.legacy import _dict_utils + from weave.legacy.weave import _dict_utils cols: KeyTree = {} all_keys = False diff --git a/weave/legacy/context.py b/weave/legacy/weave/context.py similarity index 88% rename from weave/legacy/context.py rename to weave/legacy/weave/context.py index ab01ec33727..c45c255f83b 100644 --- a/weave/legacy/context.py +++ b/weave/legacy/weave/context.py @@ -4,15 +4,15 @@ import typing from urllib.parse import urlparse -from weave.legacy import util -from weave.legacy import urls, client, context_state -from weave.legacy.client_interface import ClientInterface +from weave.legacy.weave import util +from weave.legacy.weave import urls, client, context_state +from weave.legacy.weave.client_interface import ClientInterface @contextlib.contextmanager def execution_client(): """Returns a client for use by the execution engine and op resolvers.""" - from weave.legacy import server + from weave.legacy.weave import server # Force in process execution with context_state.client(client.NonCachingClient(server.InProcessServer())): @@ -22,7 +22,7 @@ def execution_client(): @contextlib.contextmanager def local_http_client(): - from weave.legacy import server + from weave.legacy.weave import server s = server.HttpServer() s.start() @@ -34,7 +34,7 @@ def local_http_client(): @contextlib.contextmanager def weavejs_client(): - from weave.legacy import server + from weave.legacy.weave import server s = server.HttpServer() s.start() @@ -48,7 +48,7 @@ def use_fixed_server_port(): # s = server.HttpServer(port=9994) # s.start() # _weave_client.set(server.HttpServerClient(s.url)) - from weave.legacy import server + from weave.legacy.weave import server context_state.set_client(server.HttpServerClient("http://localhost:9994")) @@ -70,7 +70,7 @@ def use_lazy_execution(): def _make_default_client(): - from weave.legacy import server + from weave.legacy.weave import server if util.is_notebook(): serv = context_state.get_server() @@ -105,7 +105,7 @@ def get_client() -> typing.Optional[ClientInterface]: def get_frontend_url(): - from weave.legacy import server + from weave.legacy.weave import server url = os.environ.get("WEAVE_FRONTEND_URL", context_state.get_frontend_url()) if url is None: diff --git a/weave/legacy/context_state.py b/weave/legacy/weave/context_state.py similarity index 98% rename from weave/legacy/context_state.py rename to weave/legacy/weave/context_state.py index 15741b77987..4aef897ed5d 100644 --- a/weave/legacy/context_state.py +++ b/weave/legacy/weave/context_state.py @@ -3,11 +3,11 @@ import dataclasses import typing -from weave.legacy import server_interface +from weave.legacy.weave import server_interface if typing.TYPE_CHECKING: - from weave.legacy import client_interface, uris - from weave.legacy.graph_client import GraphClient + from weave.legacy.weave import client_interface, uris + from weave.legacy.weave.graph_client import GraphClient # colab currently runs ipykernel < 6.0. This resets context on every diff --git a/weave/legacy/weave/core_types/__init__.py b/weave/legacy/weave/core_types/__init__.py new file mode 100644 index 00000000000..0cee69ecdca --- /dev/null +++ b/weave/legacy/weave/core_types/__init__.py @@ -0,0 +1 @@ +from weave.legacy.weave.core_types.stream_table_type import StreamTableType diff --git a/weave/legacy/core_types/stream_table_type.py b/weave/legacy/weave/core_types/stream_table_type.py similarity index 78% rename from weave/legacy/core_types/stream_table_type.py rename to weave/legacy/weave/core_types/stream_table_type.py index c8c3eb7802b..40a2250511e 100644 --- a/weave/legacy/core_types/stream_table_type.py +++ b/weave/legacy/weave/core_types/stream_table_type.py @@ -1,4 +1,4 @@ -from weave.legacy import decorator_type +from weave.legacy.weave import decorator_type @decorator_type.type( diff --git a/weave/legacy/debug.py b/weave/legacy/weave/debug.py similarity index 100% rename from weave/legacy/debug.py rename to weave/legacy/weave/debug.py diff --git a/weave/legacy/debug_compile.py b/weave/legacy/weave/debug_compile.py similarity index 97% rename from weave/legacy/debug_compile.py rename to weave/legacy/weave/debug_compile.py index 1f3ebcf0024..c5d75e8ff77 100644 --- a/weave/legacy/debug_compile.py +++ b/weave/legacy/weave/debug_compile.py @@ -1,5 +1,5 @@ from weave import types -from weave.legacy import graph +from weave.legacy.weave import graph from . import debug_types diff --git a/weave/legacy/debug_types.py b/weave/legacy/weave/debug_types.py similarity index 97% rename from weave/legacy/debug_types.py rename to weave/legacy/weave/debug_types.py index c2a77b2c0cf..4449bfef71c 100644 --- a/weave/legacy/debug_types.py +++ b/weave/legacy/weave/debug_types.py @@ -1,7 +1,7 @@ import textwrap -from weave.legacy.language_features.tagging import tagged_value_type -from weave.legacy.weave_types import * +from weave.legacy.weave.language_features.tagging import tagged_value_type +from weave.legacy.weave.weave_types import * def short_type(type: Type) -> str: diff --git a/weave/legacy/decorator_arrow_op.py b/weave/legacy/weave/decorator_arrow_op.py similarity index 96% rename from weave/legacy/decorator_arrow_op.py rename to weave/legacy/weave/decorator_arrow_op.py index 51869d03760..0b9ba138c69 100644 --- a/weave/legacy/decorator_arrow_op.py +++ b/weave/legacy/weave/decorator_arrow_op.py @@ -1,9 +1,9 @@ import typing -from weave.legacy import weave_types as types -from weave.legacy import op_def -from weave.legacy.arrow.arrow import ArrowWeaveListType -from weave.legacy.language_features.tagging import tagged_value_type +from weave.legacy.weave import weave_types as types +from weave.legacy.weave import op_def +from weave.legacy.weave.arrow.arrow import ArrowWeaveListType +from weave.legacy.weave.language_features.tagging import tagged_value_type from .decorator_op import op diff --git a/weave/legacy/decorator_class.py b/weave/legacy/weave/decorator_class.py similarity index 93% rename from weave/legacy/decorator_class.py rename to weave/legacy/weave/decorator_class.py index c2b2d5314d2..7f6d82f2728 100644 --- a/weave/legacy/decorator_class.py +++ b/weave/legacy/weave/decorator_class.py @@ -1,9 +1,9 @@ import inspect import typing -from weave.legacy import errors -from weave.legacy import weave_types as types -from weave.legacy import context_state, derive_op, op_def, registry_mem +from weave.legacy.weave import errors +from weave.legacy.weave import weave_types as types +from weave.legacy.weave import context_state, derive_op, op_def, registry_mem # Contrary to the way it is read, the weave.class() decorator runs AFTER the # inner methods are defined. Therefore, this function runs after the ops are diff --git a/weave/legacy/decorator_mutation.py b/weave/legacy/weave/decorator_mutation.py similarity index 100% rename from weave/legacy/decorator_mutation.py rename to weave/legacy/weave/decorator_mutation.py diff --git a/weave/legacy/decorator_op.py b/weave/legacy/weave/decorator_op.py similarity index 93% rename from weave/legacy/decorator_op.py rename to weave/legacy/weave/decorator_op.py index d0081d8494a..e7a156614d6 100644 --- a/weave/legacy/decorator_op.py +++ b/weave/legacy/weave/decorator_op.py @@ -4,11 +4,11 @@ from typing_extensions import ParamSpec -from weave.legacy import weave_types as types -from weave.legacy import context_state, derive_op, op_args, op_def, pyfunc_type_util, registry_mem +from weave.legacy.weave import weave_types as types +from weave.legacy.weave import context_state, derive_op, op_args, op_def, pyfunc_type_util, registry_mem if typing.TYPE_CHECKING: - from weave.legacy.gql_op_plugin import GqlOpPlugin + from weave.legacy.weave.gql_op_plugin import GqlOpPlugin # Important usability note @@ -104,7 +104,7 @@ def wrap(f: Callable[P, R]) -> Callable[P, R]: mutation=mutation, ) if weavify: - from weave.legacy.weavify import op_to_weave_fn + from weave.legacy.weave.weavify import op_to_weave_fn op.weave_fn = op_to_weave_fn(op) diff --git a/weave/legacy/decorator_type.py b/weave/legacy/weave/decorator_type.py similarity index 95% rename from weave/legacy/decorator_type.py rename to weave/legacy/weave/decorator_type.py index 2b761f2838f..1e437a30c2c 100644 --- a/weave/legacy/decorator_type.py +++ b/weave/legacy/weave/decorator_type.py @@ -2,9 +2,9 @@ import inspect import typing -from weave.legacy import errors -from weave.legacy import weave_types as types -from weave.legacy import context_state, decorator_class, object_type_ref_util, infer_types +from weave.legacy.weave import errors +from weave.legacy.weave import weave_types as types +from weave.legacy.weave import context_state, decorator_class, object_type_ref_util, infer_types _py_type = type @@ -93,7 +93,7 @@ def wrap(target): # and deserialize them along with the data attached to the object if relocatable: for name, member in inspect.getmembers(target): - from weave.legacy import op_def, op_def_type + from weave.legacy.weave import op_def, op_def_type if isinstance(member, op_def.BoundOpDef): static_property_types[name] = op_def_type.OpDefType() diff --git a/weave/legacy/decorator_type.pyi b/weave/legacy/weave/decorator_type.pyi similarity index 100% rename from weave/legacy/decorator_type.pyi rename to weave/legacy/weave/decorator_type.pyi diff --git a/weave/legacy/decorators.py b/weave/legacy/weave/decorators.py similarity index 100% rename from weave/legacy/decorators.py rename to weave/legacy/weave/decorators.py diff --git a/weave/legacy/derive_op.py b/weave/legacy/weave/derive_op.py similarity index 98% rename from weave/legacy/derive_op.py rename to weave/legacy/weave/derive_op.py index dcf1010b024..05de8dc9309 100644 --- a/weave/legacy/derive_op.py +++ b/weave/legacy/weave/derive_op.py @@ -6,12 +6,10 @@ import inspect import typing -from weave import ( +from weave.legacy.weave import weave_types as types +from weave.legacy.weave import ( storage, weave_internal, -) -from weave.legacy import weave_types as types -from weave.legacy import ( errors, parallelism, registry_mem, @@ -23,7 +21,7 @@ op_def, op_policy, ) -from weave.legacy.language_features.tagging import tag_store +from weave.legacy.weave.language_features.tagging import tag_store USE_PARALLEL_DOWNLOAD = True USE_PARALLEL_REFINE = True @@ -197,7 +195,7 @@ def make_output_type(input_types): raise errors.WeaveMakeFunctionError( "output_type function must return a node." ) - from weave.legacy.ops_primitives.list_ import make_list + from weave.legacy.weave.ops_primitives.list_ import make_list return types.List.make( { diff --git a/weave/legacy/dispatch.py b/weave/legacy/weave/dispatch.py similarity index 98% rename from weave/legacy/dispatch.py rename to weave/legacy/weave/dispatch.py index 39baffd75a8..3017187613b 100644 --- a/weave/legacy/dispatch.py +++ b/weave/legacy/weave/dispatch.py @@ -5,11 +5,11 @@ import typing from dataclasses import dataclass -from weave.legacy import util -from weave.legacy import weave_types as types -from weave.legacy import graph, memo, op_args, op_def, pyfunc_type_util, registry_mem, errors -from weave.legacy.language_features.tagging.is_tag_getter import is_tag_getter -from weave.legacy.language_features.tagging.tagged_value_type import TaggedValueType +from weave.legacy.weave import util +from weave.legacy.weave import weave_types as types +from weave.legacy.weave import graph, memo, op_args, op_def, pyfunc_type_util, registry_mem, errors +from weave.legacy.weave.language_features.tagging.is_tag_getter import is_tag_getter +from weave.legacy.weave.language_features.tagging.tagged_value_type import TaggedValueType # I originally wrote this thinking that we could always choose the more specific diff --git a/weave/legacy/eager.py b/weave/legacy/weave/eager.py similarity index 93% rename from weave/legacy/eager.py rename to weave/legacy/weave/eager.py index 88ea1c453b6..393e23ad23f 100644 --- a/weave/legacy/eager.py +++ b/weave/legacy/weave/eager.py @@ -1,9 +1,9 @@ import typing -from weave.legacy import context_state, graph +from weave.legacy.weave import context_state, graph -from .. import weave_internal -from weave.legacy import weave_types as types +from weave.legacy.weave import weave_internal +from weave.legacy.weave import weave_types as types WeaveIterObjectType = typing.TypeVar("WeaveIterObjectType") @@ -81,7 +81,7 @@ def __iter__(self) -> typing.Iterator[WeaveIterObjectType]: def select_all(node: graph.Node) -> graph.Node: if not types.TypedDict().assign_type(node.type): raise ValueError("only TypedDict supported for now") - from weave.legacy.ops_primitives import dict_ + from weave.legacy.weave.ops_primitives import dict_ node_type = typing.cast(types.TypedDict, node.type) return dict_(**{k: node[k] for k in node_type.property_types}) # type: ignore diff --git a/weave/legacy/weave/ecosystem/__init__.py b/weave/legacy/weave/ecosystem/__init__.py new file mode 100644 index 00000000000..2ba3a1f0da9 --- /dev/null +++ b/weave/legacy/weave/ecosystem/__init__.py @@ -0,0 +1 @@ +from weave.legacy.weave.ecosystem.root import * diff --git a/weave/legacy/ecosystem/all.py b/weave/legacy/weave/ecosystem/all.py similarity index 90% rename from weave/legacy/ecosystem/all.py rename to weave/legacy/weave/ecosystem/all.py index 0fd564f8d46..e1e5c1d39f6 100644 --- a/weave/legacy/ecosystem/all.py +++ b/weave/legacy/weave/ecosystem/all.py @@ -4,7 +4,7 @@ import typing from collections import Counter, defaultdict -from weave.legacy import context_state +from weave.legacy.weave import context_state logger = logging.getLogger(__name__) @@ -38,11 +38,11 @@ LOAD_RESULTS: typing.DefaultDict[str, list[str]] = defaultdict(list[str]) try: - logger.info("Loading weave.legacy.ecosystem") + logger.info("Loading weave.legacy.weave.ecosystem") for module in ALL_MODULES: try: globals()[module] = importlib.import_module( - f"weave.legacy.ecosystem.{module}" + f"weave.legacy.weave.ecosystem.{module}" ) LOAD_RESULTS["loaded"].append(module) except ImportError as exc: diff --git a/weave/legacy/weave/ecosystem/bertviz/__init__.py b/weave/legacy/weave/ecosystem/bertviz/__init__.py new file mode 100644 index 00000000000..e220f65bfaf --- /dev/null +++ b/weave/legacy/weave/ecosystem/bertviz/__init__.py @@ -0,0 +1 @@ +from weave.legacy.weave.ecosystem.bertviz.panels import * diff --git a/weave/legacy/ecosystem/bertviz/panels.py b/weave/legacy/weave/ecosystem/bertviz/panels.py similarity index 83% rename from weave/legacy/ecosystem/bertviz/panels.py rename to weave/legacy/weave/ecosystem/bertviz/panels.py index d4b03c4bf76..36a6a63c0c9 100644 --- a/weave/legacy/ecosystem/bertviz/panels.py +++ b/weave/legacy/weave/ecosystem/bertviz/panels.py @@ -1,11 +1,11 @@ import bertviz import weave -from weave.legacy.ecosystem import huggingface +from weave.legacy.weave.ecosystem import huggingface @weave.op() -def head_view(attention: huggingface.ModelOutputAttention) -> weave.legacy.ops.Html: +def head_view(attention: huggingface.ModelOutputAttention) -> weave.legacy.weave.ops.Html: # All the information we need is attached to the ModelOutputAttention object. # This is important. In Weave, types should "stand alone", meaning they should # contain references to any information that is necessary for their use. @@ -27,7 +27,7 @@ def head_view(attention: huggingface.ModelOutputAttention) -> weave.legacy.ops.H # The .data attribute contains the html string. Wrap it in the weave Html type. # TODO: this would read better as weave.types.Html I think. - return weave.legacy.ops.Html(html.data) + return weave.legacy.weave.ops.Html(html.data) @weave.type() @@ -37,18 +37,18 @@ class BertvizHeadView(weave.Panel): input_node: weave.Node[huggingface.ModelOutputAttention] @weave.op() - def render(self) -> weave.legacy.panels.PanelHtml: + def render(self) -> weave.legacy.weave.panels.PanelHtml: # This is a lazy call! It doesn't execute anything html = head_view(self.input_node) # We add the lazy call as input to the returned Html panel. Nothing has been # computed so far. The UI's Html panel will perform a useNodeValue operation on its # input node. Only then will the head_view function finally be called. - return weave.legacy.panels.PanelHtml(html) + return weave.legacy.weave.panels.PanelHtml(html) @weave.op() -def model_view(attention: huggingface.ModelOutputAttention) -> weave.legacy.ops.Html: +def model_view(attention: huggingface.ModelOutputAttention) -> weave.legacy.weave.ops.Html: # Parallels head_view() to visualize the full matrix of attention heads as rows # and layers as columns for each attention map @@ -61,7 +61,7 @@ def model_view(attention: huggingface.ModelOutputAttention) -> weave.legacy.ops. html = bertviz.model_view(attention._attention, tokens, html_action="return") # TODO: this would read better as weave.types.Html I think. - return weave.legacy.ops.Html(html.data) + return weave.legacy.weave.ops.Html(html.data) @weave.type() @@ -71,6 +71,6 @@ class BertvizModelView(weave.Panel): input_node: weave.Node[huggingface.ModelOutputAttention] @weave.op() - def model_view_panel_render(self) -> weave.legacy.panels.PanelHtml: + def model_view_panel_render(self) -> weave.legacy.weave.panels.PanelHtml: html = model_view(self.input_node) - return weave.legacy.panels.PanelHtml(html) + return weave.legacy.weave.panels.PanelHtml(html) diff --git a/weave/legacy/weave/ecosystem/craiyon/__init__.py b/weave/legacy/weave/ecosystem/craiyon/__init__.py new file mode 100644 index 00000000000..dbcad02cfd5 --- /dev/null +++ b/weave/legacy/weave/ecosystem/craiyon/__init__.py @@ -0,0 +1 @@ +from weave.legacy.weave.ecosystem.craiyon.ops import * diff --git a/weave/legacy/ecosystem/craiyon/ops.py b/weave/legacy/weave/ecosystem/craiyon/ops.py similarity index 100% rename from weave/legacy/ecosystem/craiyon/ops.py rename to weave/legacy/weave/ecosystem/craiyon/ops.py diff --git a/weave/legacy/ecosystem/hdbscan/__init__.py b/weave/legacy/weave/ecosystem/example/__init__.py similarity index 73% rename from weave/legacy/ecosystem/hdbscan/__init__.py rename to weave/legacy/weave/ecosystem/example/__init__.py index 4481af6f01c..2ae52ad30e0 100644 --- a/weave/legacy/ecosystem/hdbscan/__init__.py +++ b/weave/legacy/weave/ecosystem/example/__init__.py @@ -2,13 +2,13 @@ import logging -from weave.legacy import context_state +from weave.legacy.weave import context_state logging.getLogger("ecosystem_example").setLevel(logging.ERROR) loading_builtins_token = context_state.set_loading_built_ins() try: - from weave.legacy.ecosystem.hdbscan.ops import * + from weave.legacy.weave.ecosystem.example import ops finally: context_state.clear_loading_built_ins(loading_builtins_token) diff --git a/weave/legacy/ecosystem/example/ops.py b/weave/legacy/weave/ecosystem/example/ops.py similarity index 100% rename from weave/legacy/ecosystem/example/ops.py rename to weave/legacy/weave/ecosystem/example/ops.py diff --git a/weave/legacy/ecosystem/umap/__init__.py b/weave/legacy/weave/ecosystem/hdbscan/__init__.py similarity index 73% rename from weave/legacy/ecosystem/umap/__init__.py rename to weave/legacy/weave/ecosystem/hdbscan/__init__.py index 820a584fa5a..62b6fee8188 100644 --- a/weave/legacy/ecosystem/umap/__init__.py +++ b/weave/legacy/weave/ecosystem/hdbscan/__init__.py @@ -2,13 +2,13 @@ import logging -from weave.legacy import context_state +from weave.legacy.weave import context_state logging.getLogger("ecosystem_example").setLevel(logging.ERROR) loading_builtins_token = context_state.set_loading_built_ins() try: - from weave.legacy.ecosystem.umap.ops import * + from weave.legacy.weave.ecosystem.hdbscan.ops import * finally: context_state.clear_loading_built_ins(loading_builtins_token) diff --git a/weave/legacy/ecosystem/hdbscan/ops.py b/weave/legacy/weave/ecosystem/hdbscan/ops.py similarity index 95% rename from weave/legacy/ecosystem/hdbscan/ops.py rename to weave/legacy/weave/ecosystem/hdbscan/ops.py index 5dd0ae5184b..e12213661a9 100644 --- a/weave/legacy/ecosystem/hdbscan/ops.py +++ b/weave/legacy/weave/ecosystem/hdbscan/ops.py @@ -10,7 +10,7 @@ ) import weave -from weave.legacy import ops_arrow +from weave.legacy.weave import ops_arrow class HDBSCANOptions(TypedDict): diff --git a/weave/legacy/weave/ecosystem/huggingface/__init__.py b/weave/legacy/weave/ecosystem/huggingface/__init__.py new file mode 100644 index 00000000000..8e6bc2c2ae7 --- /dev/null +++ b/weave/legacy/weave/ecosystem/huggingface/__init__.py @@ -0,0 +1,12 @@ +from weave.legacy.weave import context_state as _context + +_loading_builtins_token = _context.set_loading_built_ins() + +from weave.legacy.weave.ecosystem.huggingface.hfmodel import ModelOutputAttention +from weave.legacy.weave.ecosystem.huggingface.huggingface_datasets import * +from weave.legacy.weave.ecosystem.huggingface.huggingface_models import * +from weave.legacy.weave.ecosystem.huggingface.model_textclassification import ( + FullTextClassificationPipelineOutput, +) + +_context.clear_loading_built_ins(_loading_builtins_token) diff --git a/weave/legacy/ecosystem/huggingface/hfmodel.py b/weave/legacy/weave/ecosystem/huggingface/hfmodel.py similarity index 96% rename from weave/legacy/ecosystem/huggingface/hfmodel.py rename to weave/legacy/weave/ecosystem/huggingface/hfmodel.py index 65debd9f6e6..7a521299a8e 100644 --- a/weave/legacy/ecosystem/huggingface/hfmodel.py +++ b/weave/legacy/weave/ecosystem/huggingface/hfmodel.py @@ -7,8 +7,8 @@ import transformers import weave -from weave.legacy import op_def_type -from weave.legacy.ecosystem import pytorch +from weave.legacy.weave import op_def_type +from weave.legacy.weave.ecosystem import pytorch # This tells Weave how to serialize BaseModelOutput @@ -72,7 +72,7 @@ def tokenizer(self): return transformers.AutoTokenizer.from_pretrained(self._id) @weave.op() - def readme(self) -> weave.legacy.ops.Markdown: + def readme(self) -> weave.legacy.weave.ops.Markdown: readme = huggingface_hub.hf_hub_download(self._id, "README.md") # quick hack: remove the metadata header from the readme readme_contents = "" @@ -85,7 +85,7 @@ def readme(self) -> weave.legacy.ops.Markdown: # if we failed to parse out the header if len(readme_contents) < 1: readme_contents = open(readme).read() - return weave.legacy.ops.Markdown(readme_contents) + return weave.legacy.weave.ops.Markdown(readme_contents) @weave.op() def id(self) -> str: diff --git a/weave/legacy/ecosystem/huggingface/huggingface_datasets.py b/weave/legacy/weave/ecosystem/huggingface/huggingface_datasets.py similarity index 98% rename from weave/legacy/ecosystem/huggingface/huggingface_datasets.py rename to weave/legacy/weave/ecosystem/huggingface/huggingface_datasets.py index d47614eee07..f29bf154d78 100644 --- a/weave/legacy/ecosystem/huggingface/huggingface_datasets.py +++ b/weave/legacy/weave/ecosystem/huggingface/huggingface_datasets.py @@ -41,7 +41,7 @@ def hf_feature_type_to_type(type_): elif isinstance(type_, datasets.features.features.Sequence): return weave.types.List(hf_feature_type_to_type(type_.feature)) elif isinstance(type_, datasets.features.features.Image): - return weave.legacy.ops.PILImageType() + return weave.legacy.weave.ops.PILImageType() elif isinstance(type_, datasets.features.features.ClassLabel): # TODO: this should be a classes type!!!!! return weave.types.Int() diff --git a/weave/legacy/ecosystem/huggingface/huggingface_models.py b/weave/legacy/weave/ecosystem/huggingface/huggingface_models.py similarity index 78% rename from weave/legacy/ecosystem/huggingface/huggingface_models.py rename to weave/legacy/weave/ecosystem/huggingface/huggingface_models.py index f803fd446b9..8fa4865628b 100644 --- a/weave/legacy/ecosystem/huggingface/huggingface_models.py +++ b/weave/legacy/weave/ecosystem/huggingface/huggingface_models.py @@ -7,8 +7,8 @@ import transformers import weave -from weave.legacy.ecosystem import pytorch -from weave.legacy.ecosystem.huggingface import ( +from weave.legacy.weave.ecosystem import pytorch +from weave.legacy.weave.ecosystem.huggingface import ( hfmodel, model_textclassification, model_textgeneration, @@ -40,11 +40,11 @@ class HuggingfaceModelsPanel(weave.Panel): input_node: weave.Node[list[hfmodel.HFModel]] @weave.op() - def render(self) -> weave.legacy.panels.Table: - return weave.legacy.panels.Table( + def render(self) -> weave.legacy.weave.panels.Table: + return weave.legacy.weave.panels.Table( self.input_node, columns=[ - lambda model_row: weave.legacy.panels.WeaveLink( + lambda model_row: weave.legacy.weave.panels.WeaveLink( model_row.id(), to=lambda input: huggingface().model(input), # type: ignore ), @@ -64,24 +64,24 @@ class HuggingfaceModelPanel(weave.Panel): input_node: weave.Node[hfmodel.HFModel] @weave.op(pure=False) - def render(self) -> weave.legacy.panels.Card: + def render(self) -> weave.legacy.weave.panels.Card: model = typing.cast(hfmodel.HFModel, self.input_node) - return weave.legacy.panels.Card( + return weave.legacy.weave.panels.Card( title=model.id(), subtitle="HuggingFace Hub Model", content=[ - weave.legacy.panels.CardTab( + weave.legacy.weave.panels.CardTab( name="Model Card", - content=weave.legacy.panels.PanelMarkdown(model.readme()), # type: ignore + content=weave.legacy.weave.panels.PanelMarkdown(model.readme()), # type: ignore ), - weave.legacy.panels.CardTab( + weave.legacy.weave.panels.CardTab( name="Metadata", - content=weave.legacy.panels.Group( + content=weave.legacy.weave.panels.Group( items={ - "id": weave.legacy.panels.LabeledItem( + "id": weave.legacy.weave.panels.LabeledItem( item=model.id(), label="ID" ), - "pipeline_tag": weave.legacy.panels.LabeledItem( + "pipeline_tag": weave.legacy.weave.panels.LabeledItem( item=model.pipeline_tag(), label="Pipeline tag" ), } @@ -89,10 +89,10 @@ def render(self) -> weave.legacy.panels.Card: ), # Broke in panel refactor. Don't have concrete op name available here so # can't get the right type for the output. - # weave.legacy.panels.CardTab( + # weave.legacy.weave.panels.CardTab( # name="Inference Logs", - # content=weave.legacy.panels.Table( - # weave.legacy.ops.used_by(model, model.call.op_name()), + # content=weave.legacy.weave.panels.Table( + # weave.legacy.weave.ops.used_by(model, model.call.op_name()), # columns=[ # lambda run: run.output.model_input, # lambda run: run.output.model_output[0]["generated_text"], @@ -141,12 +141,12 @@ class HuggingfacePackagePanel(weave.Panel): input_node: weave.Node[HuggingFacePackage] @weave.op() - def render(self) -> weave.legacy.panels.Card: + def render(self) -> weave.legacy.weave.panels.Card: pack = typing.cast(HuggingFacePackage, self.input_node) # type: ignore - return weave.legacy.panels.Card( + return weave.legacy.weave.panels.Card( title="Huggingface Package", subtitle="Browse Models and Datasets", content=[ - weave.legacy.panels.CardTab(name="Models", content=pack.models()), # type: ignore + weave.legacy.weave.panels.CardTab(name="Models", content=pack.models()), # type: ignore ], ) diff --git a/weave/legacy/ecosystem/huggingface/model_textclassification.py b/weave/legacy/weave/ecosystem/huggingface/model_textclassification.py similarity index 89% rename from weave/legacy/ecosystem/huggingface/model_textclassification.py rename to weave/legacy/weave/ecosystem/huggingface/model_textclassification.py index 40a7a496109..4101b36849e 100644 --- a/weave/legacy/ecosystem/huggingface/model_textclassification.py +++ b/weave/legacy/weave/ecosystem/huggingface/model_textclassification.py @@ -4,7 +4,7 @@ import transformers import weave -from weave.legacy.ecosystem.huggingface import hfmodel +from weave.legacy.weave.ecosystem.huggingface import hfmodel # We have to forward-declare the Weave types to avoid circular reference # issues that weave.type() can't resolve yet. @@ -70,13 +70,13 @@ class ClassificationResultPanel(weave.Panel): input_node: weave.Node[list[ClassificationResult]] @weave.op() - def render(self) -> weave.legacy.panels.Table: - from weave.legacy.ecosystem.huggingface.huggingface_models import huggingface + def render(self) -> weave.legacy.weave.panels.Table: + from weave.legacy.weave.ecosystem.huggingface.huggingface_models import huggingface - return weave.legacy.panels.Table( + return weave.legacy.weave.panels.Table( self.input_node, columns=[ - lambda result_row: weave.legacy.panels.WeaveLink( + lambda result_row: weave.legacy.weave.panels.WeaveLink( result_row.model_name(), to=lambda input: huggingface().model(input), # type: ignore ), @@ -115,17 +115,17 @@ class FullTextClassificationResultPanel(weave.Panel): input_node: weave.Node[FullTextClassificationPipelineOutput] @weave.op() - def render(self) -> weave.legacy.panels.Group: + def render(self) -> weave.legacy.weave.panels.Group: output = typing.cast(FullTextClassificationPipelineOutput, self.input_node) - return weave.legacy.panels.Group( + return weave.legacy.weave.panels.Group( preferHorizontal=True, items={ - "input": weave.legacy.panels.LabeledItem( + "input": weave.legacy.weave.panels.LabeledItem( label="input", item=output.model_input ), - "output": weave.legacy.panels.LabeledItem( + "output": weave.legacy.weave.panels.LabeledItem( label="output", - item=weave.legacy.panels.Plot( + item=weave.legacy.weave.panels.Plot( input_node=typing.cast(weave.Node, output.model_output), x=lambda class_score: class_score["score"], y=lambda class_score: class_score["label"], diff --git a/weave/legacy/ecosystem/huggingface/model_textgeneration.py b/weave/legacy/weave/ecosystem/huggingface/model_textgeneration.py similarity index 91% rename from weave/legacy/ecosystem/huggingface/model_textgeneration.py rename to weave/legacy/weave/ecosystem/huggingface/model_textgeneration.py index edbaa739cf2..a422e88f1c2 100644 --- a/weave/legacy/ecosystem/huggingface/model_textgeneration.py +++ b/weave/legacy/weave/ecosystem/huggingface/model_textgeneration.py @@ -4,7 +4,7 @@ import transformers import weave -from weave.legacy.ecosystem.huggingface import hfmodel +from weave.legacy.weave.ecosystem.huggingface import hfmodel # We have to forward-declare the Weave types to avoid circular reference # issues that weave.type() can't resolve yet. @@ -50,15 +50,15 @@ class FullTextGenerationPanel(weave.Panel): input_node: weave.Node[FullTextGenerationPipelineOutput] @weave.op() - def render(self) -> weave.legacy.panels.Group: + def render(self) -> weave.legacy.weave.panels.Group: output = typing.cast(FullTextGenerationPipelineOutput, self.input_node) - return weave.legacy.panels.Group( + return weave.legacy.weave.panels.Group( preferHorizontal=True, items={ - "input": weave.legacy.panels.LabeledItem( + "input": weave.legacy.weave.panels.LabeledItem( label="input", item=output.model_input ), - "output": weave.legacy.panels.LabeledItem( + "output": weave.legacy.weave.panels.LabeledItem( label="output", item=output.model_output ), }, diff --git a/weave/legacy/ecosystem/xgboost/__init__.py b/weave/legacy/weave/ecosystem/keras/__init__.py similarity index 51% rename from weave/legacy/ecosystem/xgboost/__init__.py rename to weave/legacy/weave/ecosystem/keras/__init__.py index 361df7d9d64..c27c01c5e63 100644 --- a/weave/legacy/ecosystem/xgboost/__init__.py +++ b/weave/legacy/weave/ecosystem/keras/__init__.py @@ -1,7 +1,6 @@ -from weave.legacy import context_state as _context +from weave.legacy.weave import context_state as _context _loading_builtins_token = _context.set_loading_built_ins() - -from weave.legacy.ecosystem.xgboost.model import * +from weave.legacy.weave.ecosystem.keras.model import * _context.clear_loading_built_ins(_loading_builtins_token) diff --git a/weave/legacy/ecosystem/keras/model.py b/weave/legacy/weave/ecosystem/keras/model.py similarity index 100% rename from weave/legacy/ecosystem/keras/model.py rename to weave/legacy/weave/ecosystem/keras/model.py diff --git a/weave/legacy/ecosystem/langchain/__init__.py b/weave/legacy/weave/ecosystem/langchain/__init__.py similarity index 67% rename from weave/legacy/ecosystem/langchain/__init__.py rename to weave/legacy/weave/ecosystem/langchain/__init__.py index 49a02f9cac1..104095d21f5 100644 --- a/weave/legacy/ecosystem/langchain/__init__.py +++ b/weave/legacy/weave/ecosystem/langchain/__init__.py @@ -1,12 +1,12 @@ import logging -from weave.legacy import context_state +from weave.legacy.weave import context_state logging.getLogger("langchain").setLevel(logging.ERROR) loading_builtins_token = context_state.set_loading_built_ins() try: - from weave.legacy.ecosystem.langchain.lc import * + from weave.legacy.weave.ecosystem.langchain.lc import * finally: context_state.clear_loading_built_ins(loading_builtins_token) diff --git a/weave/legacy/ecosystem/langchain/lc.py b/weave/legacy/weave/ecosystem/langchain/lc.py similarity index 98% rename from weave/legacy/ecosystem/langchain/lc.py rename to weave/legacy/weave/ecosystem/langchain/lc.py index 46fd02f6a65..51fc011c7fb 100644 --- a/weave/legacy/ecosystem/langchain/lc.py +++ b/weave/legacy/weave/ecosystem/langchain/lc.py @@ -41,12 +41,12 @@ from langchain.vectorstores.base import VectorStoreRetriever import weave -from weave.legacy import storage -from weave.legacy import artifact_base -from weave.legacy.artifact_fs import FilesystemArtifact -from weave.legacy.ecosystem.langchain import util -from weave.legacy.ops_domain import trace_tree -from weave.legacy.weave_types import Type +from weave.legacy.weave import storage +from weave.legacy.weave import artifact_base +from weave.legacy.weave.artifact_fs import FilesystemArtifact +from weave.legacy.weave.ecosystem.langchain import util +from weave.legacy.weave.ops_domain import trace_tree +from weave.legacy.weave.weave_types import Type class WeaveTracer(BaseTracer): diff --git a/weave/legacy/ecosystem/langchain/util.py b/weave/legacy/weave/ecosystem/langchain/util.py similarity index 99% rename from weave/legacy/ecosystem/langchain/util.py rename to weave/legacy/weave/ecosystem/langchain/util.py index 4341c64995f..b25b592f398 100644 --- a/weave/legacy/ecosystem/langchain/util.py +++ b/weave/legacy/weave/ecosystem/langchain/util.py @@ -13,7 +13,7 @@ from langchain.callbacks.tracers.schemas import Run, RunTypeEnum -from weave.legacy.ops_domain import trace_tree +from weave.legacy.weave.ops_domain import trace_tree if TYPE_CHECKING: from langchain.chains.base import Chain diff --git a/weave/legacy/weave/ecosystem/lens/__init__.py b/weave/legacy/weave/ecosystem/lens/__init__.py new file mode 100644 index 00000000000..4c04886f8c2 --- /dev/null +++ b/weave/legacy/weave/ecosystem/lens/__init__.py @@ -0,0 +1 @@ +from weave.legacy.weave.ecosystem.lens.lens import * diff --git a/weave/legacy/ecosystem/lens/lens.py b/weave/legacy/weave/ecosystem/lens/lens.py similarity index 100% rename from weave/legacy/ecosystem/lens/lens.py rename to weave/legacy/weave/ecosystem/lens/lens.py diff --git a/weave/legacy/weave/ecosystem/py/__init__.py b/weave/legacy/weave/ecosystem/py/__init__.py new file mode 100644 index 00000000000..e535e18a474 --- /dev/null +++ b/weave/legacy/weave/ecosystem/py/__init__.py @@ -0,0 +1 @@ +from weave.legacy.weave.ecosystem.py import pydoc diff --git a/weave/legacy/ecosystem/py/pydoc.py b/weave/legacy/weave/ecosystem/py/pydoc.py similarity index 76% rename from weave/legacy/ecosystem/py/pydoc.py rename to weave/legacy/weave/ecosystem/py/pydoc.py index 946b877cb60..e558f42a5e5 100644 --- a/weave/legacy/ecosystem/py/pydoc.py +++ b/weave/legacy/weave/ecosystem/py/pydoc.py @@ -51,8 +51,8 @@ def module_name(module: types.ModuleType) -> str: @weave.op() -def module_doc(module: types.ModuleType) -> weave.legacy.ops.Markdown: - return weave.legacy.ops.Markdown(module.__doc__ or "") +def module_doc(module: types.ModuleType) -> weave.legacy.weave.ops.Markdown: + return weave.legacy.weave.ops.Markdown(module.__doc__ or "") @weave.op() @@ -93,8 +93,8 @@ def pyclass_module(pyclass: type) -> types.ModuleType: @weave.op() -def pyclass_doc(pyclass: type) -> weave.legacy.ops.Markdown: - return weave.legacy.ops.Markdown(pyclass.__doc__ or "") +def pyclass_doc(pyclass: type) -> weave.legacy.weave.ops.Markdown: + return weave.legacy.weave.ops.Markdown(pyclass.__doc__ or "") @weave.op() @@ -126,8 +126,8 @@ def function_name(pyfunction: types.FunctionType) -> str: @weave.op() -def function_doc(pyfunction: types.FunctionType) -> weave.legacy.ops.Markdown: - return weave.legacy.ops.Markdown(pyfunction.__doc__ or "") +def function_doc(pyfunction: types.FunctionType) -> weave.legacy.weave.ops.Markdown: + return weave.legacy.weave.ops.Markdown(pyfunction.__doc__ or "") @weave.type() @@ -136,34 +136,34 @@ class ModulePanel(weave.Panel): input_node: weave.Node[types.ModuleType] @weave.op() - def render(self) -> weave.legacy.panels.Card: + def render(self) -> weave.legacy.weave.panels.Card: module = self.input_node - return weave.legacy.panels.Card( + return weave.legacy.weave.panels.Card( title=module.module_name(), # type: ignore subtitle="python module", content=[ - weave.legacy.panels.CardTab( + weave.legacy.weave.panels.CardTab( name="Description", - content=weave.legacy.panels.PanelMarkdown(module.module_doc()), # type: ignore + content=weave.legacy.weave.panels.PanelMarkdown(module.module_doc()), # type: ignore ), - weave.legacy.panels.CardTab( + weave.legacy.weave.panels.CardTab( name="Classes", - content=weave.legacy.panels.Table( + content=weave.legacy.weave.panels.Table( module.module_classes(), # type: ignore columns=[ - lambda c: weave.legacy.panels.WeaveLink( + lambda c: weave.legacy.weave.panels.WeaveLink( c.class_name(), to=lambda inp: module.module_class(inp), # type: ignore ) ], ), ), - weave.legacy.panels.CardTab( + weave.legacy.weave.panels.CardTab( name="Functions", - content=weave.legacy.panels.Table( + content=weave.legacy.weave.panels.Table( module.module_functions(), # type: ignore columns=[ - lambda c: weave.legacy.panels.WeaveLink( + lambda c: weave.legacy.weave.panels.WeaveLink( c.function_name(), to=lambda inp: module.module_function(inp), # type: ignore ) @@ -180,22 +180,22 @@ class ClassPanel(weave.Panel): input_node: weave.Node[type] @weave.op() - def render(self) -> weave.legacy.panels.Card: + def render(self) -> weave.legacy.weave.panels.Card: cls = self.input_node - return weave.legacy.panels.Card( + return weave.legacy.weave.panels.Card( title=cls.class_name(), # type: ignore subtitle="python class", content=[ - weave.legacy.panels.CardTab( + weave.legacy.weave.panels.CardTab( name="Description", - content=weave.legacy.panels.PanelMarkdown(cls.pyclass_doc()), # type: ignore + content=weave.legacy.weave.panels.PanelMarkdown(cls.pyclass_doc()), # type: ignore ), - weave.legacy.panels.CardTab( + weave.legacy.weave.panels.CardTab( name="Methods", - content=weave.legacy.panels.Table( + content=weave.legacy.weave.panels.Table( cls.class_methods(), # type: ignore columns=[ - lambda m: weave.legacy.panels.WeaveLink( + lambda m: weave.legacy.weave.panels.WeaveLink( m.function_name(), to=lambda inp: cls.class_method(inp), # type: ignore ) @@ -212,15 +212,15 @@ class FunctionPanel(weave.Panel): input_node: weave.Node[types.FunctionType] @weave.op() - def render(self) -> weave.legacy.panels.Card: + def render(self) -> weave.legacy.weave.panels.Card: func = self.input_node - return weave.legacy.panels.Card( + return weave.legacy.weave.panels.Card( title=func.function_name(), # type: ignore subtitle="python function", content=[ - weave.legacy.panels.CardTab( + weave.legacy.weave.panels.CardTab( name="Description", - content=weave.legacy.panels.PanelMarkdown(func.function_doc()), # type: ignore + content=weave.legacy.weave.panels.PanelMarkdown(func.function_doc()), # type: ignore ), ], ) diff --git a/weave/legacy/weave/ecosystem/pytorch/__init__.py b/weave/legacy/weave/ecosystem/pytorch/__init__.py new file mode 100644 index 00000000000..3b5a12f7690 --- /dev/null +++ b/weave/legacy/weave/ecosystem/pytorch/__init__.py @@ -0,0 +1 @@ +from weave.legacy.weave.ecosystem.pytorch.model import * diff --git a/weave/legacy/ecosystem/pytorch/model.py b/weave/legacy/weave/ecosystem/pytorch/model.py similarity index 100% rename from weave/legacy/ecosystem/pytorch/model.py rename to weave/legacy/weave/ecosystem/pytorch/model.py diff --git a/weave/legacy/ecosystem/replicate/__init__.py b/weave/legacy/weave/ecosystem/replicate/__init__.py similarity index 57% rename from weave/legacy/ecosystem/replicate/__init__.py rename to weave/legacy/weave/ecosystem/replicate/__init__.py index e859a88c467..f957e5b171a 100644 --- a/weave/legacy/ecosystem/replicate/__init__.py +++ b/weave/legacy/weave/ecosystem/replicate/__init__.py @@ -1,8 +1,8 @@ -from weave.legacy import context_state +from weave.legacy.weave import context_state loading_builtins_token = context_state.set_loading_built_ins() try: - from weave.legacy.ecosystem.replicate.rep import * + from weave.legacy.weave.ecosystem.replicate.rep import * finally: context_state.clear_loading_built_ins(loading_builtins_token) diff --git a/weave/legacy/ecosystem/replicate/rep.py b/weave/legacy/weave/ecosystem/replicate/rep.py similarity index 100% rename from weave/legacy/ecosystem/replicate/rep.py rename to weave/legacy/weave/ecosystem/replicate/rep.py diff --git a/weave/legacy/ecosystem/root.py b/weave/legacy/weave/ecosystem/root.py similarity index 96% rename from weave/legacy/ecosystem/root.py rename to weave/legacy/weave/ecosystem/root.py index 6e67b1e8413..38fac4b03d4 100644 --- a/weave/legacy/ecosystem/root.py +++ b/weave/legacy/weave/ecosystem/root.py @@ -1,9 +1,9 @@ import typing -from weave.legacy import api as weave +from weave.legacy.weave import api as weave # TODO: Fix, these should be available from weave -from weave.legacy import context_state, op_def, ops, panel, panels +from weave.legacy.weave import context_state, op_def, ops, panel, panels loading_builtins_token = context_state.set_loading_built_ins() @@ -22,7 +22,7 @@ """ ) -from weave.legacy import registry_mem +from weave.legacy.weave import registry_mem op_org_name = registry_mem.memory_registry.get_op("user-name") @@ -63,7 +63,7 @@ def ops(self) -> list[op_def.OpDef]: # objects. @weave.op(name="op-ecosystem", render_info={"type": "function"}) def ecosystem() -> Ecosystem: - from weave.legacy import registry_mem + from weave.legacy.weave import registry_mem return Ecosystem( _orgs=[], diff --git a/weave/legacy/ecosystem/scenario/__init__.py b/weave/legacy/weave/ecosystem/scenario/__init__.py similarity index 79% rename from weave/legacy/ecosystem/scenario/__init__.py rename to weave/legacy/weave/ecosystem/scenario/__init__.py index f011e37a728..d747eae19c6 100644 --- a/weave/legacy/ecosystem/scenario/__init__.py +++ b/weave/legacy/weave/ecosystem/scenario/__init__.py @@ -1,8 +1,8 @@ import typing import weave -import weave.legacy -from weave.legacy import val_const +import weave.legacy.weave +from weave.legacy.weave import val_const class ScenarioResult(typing.TypedDict): @@ -26,14 +26,14 @@ class MetricsBankPanel(weave.Panel): input_node: weave.Node[MetricsBankInput] @weave.op() - def render(self) -> weave.legacy.panels.Each: + def render(self) -> weave.legacy.weave.panels.Each: input = typing.cast(MetricsBankInput, self.input_node) baseline = input["baseline"] candidate = input["candidate"] - joined = weave.legacy.ops.join_all( - weave.legacy.ops.make_list(l0=baseline, l1=candidate), + joined = weave.legacy.weave.ops.join_all( + weave.legacy.weave.ops.make_list(l0=baseline, l1=candidate), lambda row: row["scenario_id"], False, ) @@ -42,17 +42,17 @@ def render(self) -> weave.legacy.panels.Each: joined_keys = joined[0].keys() # The output type of difference is List["metric1" | "metric2" | "metric3"] - metrics = weave.legacy.ops.difference( + metrics = weave.legacy.weave.ops.difference( joined_keys, [val_const.const("scenario_id")] ) # TODO: broken - return weave.legacy.panels.Each( + return weave.legacy.weave.panels.Each( metrics, - render=lambda metric_name: weave.legacy.panels.Group( + render=lambda metric_name: weave.legacy.weave.panels.Group( items={ "title": metric_name, - "plot": weave.legacy.panels.Plot( + "plot": weave.legacy.weave.panels.Plot( joined, # TODO: bring this back # title=metric_name, diff --git a/weave/legacy/ecosystem/spacy/__init__.py b/weave/legacy/weave/ecosystem/shap/__init__.py similarity index 52% rename from weave/legacy/ecosystem/spacy/__init__.py rename to weave/legacy/weave/ecosystem/shap/__init__.py index c0b0f5af429..a22de778e34 100644 --- a/weave/legacy/ecosystem/spacy/__init__.py +++ b/weave/legacy/weave/ecosystem/shap/__init__.py @@ -1,7 +1,7 @@ -from weave.legacy import context_state as _context +from weave.legacy.weave import context_state as _context _loading_builtins_token = _context.set_loading_built_ins() -from weave.legacy.ecosystem.spacy.spacy import * +from weave.legacy.weave.ecosystem.shap.shap import * _context.clear_loading_built_ins(_loading_builtins_token) diff --git a/weave/legacy/ecosystem/shap/shap.py b/weave/legacy/weave/ecosystem/shap/shap.py similarity index 85% rename from weave/legacy/ecosystem/shap/shap.py rename to weave/legacy/weave/ecosystem/shap/shap.py index 8e364d32f61..812fcaaf918 100644 --- a/weave/legacy/ecosystem/shap/shap.py +++ b/weave/legacy/weave/ecosystem/shap/shap.py @@ -13,8 +13,8 @@ import xgboost import weave -from weave.legacy.ecosystem import huggingface as hf -from weave.legacy.ecosystem import xgboost as weave_xgb +from weave.legacy.weave.ecosystem import huggingface as hf +from weave.legacy.weave.ecosystem import xgboost as weave_xgb @weave.op( @@ -42,7 +42,7 @@ def summary_plot(self): path = "/tmp/shap-%s.png" % random.randrange(0, 1000000) plt.savefig(path) plt.close() - return weave.legacy.ops.LocalFile(path) + return weave.legacy.weave.ops.LocalFile(path) class ShapExplanationType(weave.types.Type): @@ -76,9 +76,9 @@ def shap_explain( @weave.op() -def shap_plot_text(shap_values: shap.Explanation) -> weave.legacy.ops.Html: +def shap_plot_text(shap_values: shap.Explanation) -> weave.legacy.weave.ops.Html: html = shap.plots.text(shap_values, display=False) - return weave.legacy.ops.Html(html) + return weave.legacy.weave.ops.Html(html) @weave.type() @@ -87,5 +87,5 @@ class ShapPlotText(weave.Panel): input_node: weave.Node[shap.Explanation] @weave.op() - def render(self) -> weave.legacy.panels.PanelHtml: - return weave.legacy.panels.PanelHtml(shap_plot_text(self.input_node)) + def render(self) -> weave.legacy.weave.panels.PanelHtml: + return weave.legacy.weave.panels.PanelHtml(shap_plot_text(self.input_node)) diff --git a/weave/legacy/weave/ecosystem/shawn/__init__.py b/weave/legacy/weave/ecosystem/shawn/__init__.py new file mode 100644 index 00000000000..53e96633366 --- /dev/null +++ b/weave/legacy/weave/ecosystem/shawn/__init__.py @@ -0,0 +1,7 @@ +from weave.legacy.weave import context_state as _context + +_loading_builtins_token = _context.set_loading_built_ins() + +from weave.legacy.weave.ecosystem.shawn import petdataset, scratch + +_context.clear_loading_built_ins(_loading_builtins_token) diff --git a/weave/legacy/ecosystem/shawn/petdataset.py b/weave/legacy/weave/ecosystem/shawn/petdataset.py similarity index 96% rename from weave/legacy/ecosystem/shawn/petdataset.py rename to weave/legacy/weave/ecosystem/shawn/petdataset.py index fc7becf7e53..9c5b85b66fb 100644 --- a/weave/legacy/ecosystem/shawn/petdataset.py +++ b/weave/legacy/weave/ecosystem/shawn/petdataset.py @@ -71,8 +71,8 @@ class PetDatasetPanel(weave.Panel): input_node: weave.Node[list[OxfordIIITPetDatasetItem]] @weave.op() - def render(self) -> weave.legacy.panels.Table: - return weave.legacy.panels.Table( + def render(self) -> weave.legacy.weave.panels.Table: + return weave.legacy.weave.panels.Table( self.input_node, columns=[ lambda item: item["id"], diff --git a/weave/legacy/ecosystem/shawn/scratch.py b/weave/legacy/weave/ecosystem/shawn/scratch.py similarity index 69% rename from weave/legacy/ecosystem/shawn/scratch.py rename to weave/legacy/weave/ecosystem/shawn/scratch.py index a200e6ff6b2..01614229fa4 100644 --- a/weave/legacy/ecosystem/shawn/scratch.py +++ b/weave/legacy/weave/ecosystem/shawn/scratch.py @@ -2,17 +2,17 @@ import typing import weave -from weave.legacy import panel, panel_util +from weave.legacy.weave import panel, panel_util @weave.op() def single_distribution( input_node: weave.Node[list[float]], -) -> weave.legacy.panels.Plot: +) -> weave.legacy.weave.panels.Plot: binned = input_node.groupby(lambda v: round(v * 10) / 10).map( # type: ignore - lambda group: weave.legacy.ops.dict_(value=group.key(), count=group.count()) + lambda group: weave.legacy.weave.ops.dict_(value=group.key(), count=group.count()) ) - return weave.legacy.panels.Plot( + return weave.legacy.weave.panels.Plot( binned, x=lambda row: row["value"], y=lambda row: row["count"], @@ -23,7 +23,7 @@ def single_distribution( @weave.type() class AdderConfig: operand: weave.Node[int] = dataclasses.field( - default_factory=lambda: weave.legacy.graph.ConstNode(weave.types.Int(), 10) + default_factory=lambda: weave.legacy.weave.graph.ConstNode(weave.types.Int(), 10) ) @@ -42,14 +42,14 @@ def adder_default_config(config: typing.Optional[AdderConfig]) -> AdderConfig: @weave.op() def adder_config( input_node: weave.Node[int], config: AdderConfig -) -> weave.legacy.panels.LabeledItem: +) -> weave.legacy.weave.panels.LabeledItem: input_val = typing.cast(int, input_node) config = adder_default_config(config) - return weave.legacy.panels.LabeledItem( + return weave.legacy.weave.panels.LabeledItem( label="operand", - item=weave.legacy.panels.Slider( - config=weave.legacy.panels.SliderConfig( - weave.legacy.ops.execute(config.operand) + item=weave.legacy.weave.panels.Slider( + config=weave.legacy.weave.panels.SliderConfig( + weave.legacy.weave.ops.execute(config.operand) ) ), ) @@ -58,10 +58,10 @@ def adder_config( @weave.op() def adder( input_node: weave.Node[int], config: AdderConfig -) -> weave.legacy.panels.LabeledItem: +) -> weave.legacy.weave.panels.LabeledItem: input_val = typing.cast(int, input_node) config = adder_default_config(config) - return weave.legacy.panels.LabeledItem(label="output", item=input_val + config.operand) # type: ignore + return weave.legacy.weave.panels.LabeledItem(label="output", item=input_val + config.operand) # type: ignore @weave.type() diff --git a/weave/legacy/weave/ecosystem/sklearn/__init__.py b/weave/legacy/weave/ecosystem/sklearn/__init__.py new file mode 100644 index 00000000000..08e6e889266 --- /dev/null +++ b/weave/legacy/weave/ecosystem/sklearn/__init__.py @@ -0,0 +1 @@ +from weave.legacy.weave.ecosystem.sklearn.datasets import * diff --git a/weave/legacy/ecosystem/sklearn/datasets.py b/weave/legacy/weave/ecosystem/sklearn/datasets.py similarity index 81% rename from weave/legacy/ecosystem/sklearn/datasets.py rename to weave/legacy/weave/ecosystem/sklearn/datasets.py index 0683b58cde1..6c5dc9e3d25 100644 --- a/weave/legacy/ecosystem/sklearn/datasets.py +++ b/weave/legacy/weave/ecosystem/sklearn/datasets.py @@ -7,7 +7,7 @@ @weave.op( name="shap-ca_housing_dataset", render_info={"type": "function"}, - output_type=weave.legacy.ops.DataFrameType(weave.types.TypedDict({})), + output_type=weave.legacy.weave.ops.DataFrameType(weave.types.TypedDict({})), ) def ca_housing_dataset(seed: int): housing = fetch_california_housing(as_frame=True) diff --git a/weave/legacy/weave/ecosystem/slack/__init__.py b/weave/legacy/weave/ecosystem/slack/__init__.py new file mode 100644 index 00000000000..a5018fce230 --- /dev/null +++ b/weave/legacy/weave/ecosystem/slack/__init__.py @@ -0,0 +1,2 @@ +from weave.legacy.weave.ecosystem.slack.panels import * +from weave.legacy.weave.ecosystem.slack.slack import * diff --git a/weave/legacy/ecosystem/slack/panels.py b/weave/legacy/weave/ecosystem/slack/panels.py similarity index 73% rename from weave/legacy/ecosystem/slack/panels.py rename to weave/legacy/weave/ecosystem/slack/panels.py index 679159a381c..41f2ee60014 100644 --- a/weave/legacy/ecosystem/slack/panels.py +++ b/weave/legacy/weave/ecosystem/slack/panels.py @@ -1,7 +1,7 @@ import typing import weave -from weave.legacy.ecosystem.slack import slack +from weave.legacy.weave.ecosystem.slack import slack @weave.type() @@ -10,9 +10,9 @@ class SlackMessagesPanel(weave.Panel): input_node: weave.Node[list[slack.Message]] @weave.op() - def render(self) -> weave.legacy.panels.Table: + def render(self) -> weave.legacy.weave.panels.Table: messages = typing.cast(list[slack.Message], self.input_node) # type: ignore - return weave.legacy.panels.Table( + return weave.legacy.weave.panels.Table( messages, columns=[ lambda message: message.user_id, @@ -27,8 +27,8 @@ class SlackChannelsPanel(weave.Panel): input_node: weave.Node[list[slack.Channel]] @weave.op() - def render(self) -> weave.legacy.panels.Table: - return weave.legacy.panels.Table( + def render(self) -> weave.legacy.weave.panels.Table: + return weave.legacy.weave.panels.Table( self.input_node, columns=[ lambda channel: channel.channel_name, @@ -43,13 +43,13 @@ class SlackChannelPanel(weave.Panel): input_node: weave.Node[slack.Channel] @weave.op() - def render(self) -> weave.legacy.panels.Card: + def render(self) -> weave.legacy.weave.panels.Card: channel = typing.cast(slack.Channel, self.input_node) # type: ignore - return weave.legacy.panels.Card( + return weave.legacy.weave.panels.Card( title=channel.channel_name, subtitle="Slack channel", content=[ - weave.legacy.panels.CardTab( + weave.legacy.weave.panels.CardTab( name="Messages", content=SlackMessagesPanel(channel.messages()) ), ], @@ -62,13 +62,13 @@ class SlackPanel(weave.Panel): input_node: weave.Node[slack.Slack] @weave.op() - def slack_render(self) -> weave.legacy.panels.Card: + def slack_render(self) -> weave.legacy.weave.panels.Card: s = typing.cast(slack.Slack, self.input_node) # type: ignore - return weave.legacy.panels.Card( + return weave.legacy.weave.panels.Card( title="Slack export data", subtitle="", content=[ - weave.legacy.panels.CardTab( + weave.legacy.weave.panels.CardTab( name="Channels", content=SlackChannelsPanel(s.channels()) ), ], diff --git a/weave/legacy/ecosystem/slack/slack.py b/weave/legacy/weave/ecosystem/slack/slack.py similarity index 97% rename from weave/legacy/ecosystem/slack/slack.py rename to weave/legacy/weave/ecosystem/slack/slack.py index fb960284021..f542dfeb021 100644 --- a/weave/legacy/ecosystem/slack/slack.py +++ b/weave/legacy/weave/ecosystem/slack/slack.py @@ -1,6 +1,6 @@ import weave from weave.file_base import Dir -from weave.legacy.ecosystem.slack import slackapi_readexport +from weave.legacy.weave.ecosystem.slack import slackapi_readexport @weave.type() diff --git a/weave/legacy/ecosystem/slack/slackapi.py b/weave/legacy/weave/ecosystem/slack/slackapi.py similarity index 100% rename from weave/legacy/ecosystem/slack/slackapi.py rename to weave/legacy/weave/ecosystem/slack/slackapi.py diff --git a/weave/legacy/ecosystem/slack/slackapi_readexport.py b/weave/legacy/weave/ecosystem/slack/slackapi_readexport.py similarity index 100% rename from weave/legacy/ecosystem/slack/slackapi_readexport.py rename to weave/legacy/weave/ecosystem/slack/slackapi_readexport.py diff --git a/weave/legacy/weave/ecosystem/slurm/__init__.py b/weave/legacy/weave/ecosystem/slurm/__init__.py new file mode 100644 index 00000000000..e9bd4eae0cd --- /dev/null +++ b/weave/legacy/weave/ecosystem/slurm/__init__.py @@ -0,0 +1 @@ +from weave.legacy.weave.ecosystem.slurm.ops import * diff --git a/weave/legacy/ecosystem/slurm/ops.py b/weave/legacy/weave/ecosystem/slurm/ops.py similarity index 79% rename from weave/legacy/ecosystem/slurm/ops.py rename to weave/legacy/weave/ecosystem/slurm/ops.py index 9611df88d73..4bd811a348f 100644 --- a/weave/legacy/ecosystem/slurm/ops.py +++ b/weave/legacy/weave/ecosystem/slurm/ops.py @@ -19,8 +19,8 @@ class SlurmJob: @weave.op() def jobs_render( jobs: weave.Node[list[SlurmJob]], -) -> weave.legacy.panels.Table: - return weave.legacy.panels.Table( +) -> weave.legacy.weave.panels.Table: + return weave.legacy.weave.panels.Table( jobs, columns=[ lambda job: job.job_id, @@ -43,8 +43,8 @@ class SlurmNode: @weave.op() def nodes_render( nodes: weave.Node[list[SlurmNode]], -) -> weave.legacy.panels.Table: - return weave.legacy.panels.Table( +) -> weave.legacy.weave.panels.Table: + return weave.legacy.weave.panels.Table( nodes, columns=[ lambda node: node.node_name, @@ -96,27 +96,27 @@ def slurm(restapi_url: str) -> Slurm: @weave.op() def slurm_render( slurm_node: weave.Node[Slurm], -) -> weave.legacy.panels.Card: +) -> weave.legacy.weave.panels.Card: slurm = typing.cast(Slurm, slurm_node) - return weave.legacy.panels.Card( + return weave.legacy.weave.panels.Card( title="slurm", subtitle="", content=[ - weave.legacy.panels.CardTab( + weave.legacy.weave.panels.CardTab( name="Overview", - content=weave.legacy.panels.Group( + content=weave.legacy.weave.panels.Group( prefer_horizontal=True, items=[ - weave.legacy.panels.LabeledItem( + weave.legacy.weave.panels.LabeledItem( item=slurm.jobs().count(), label="Total jobs" ), - weave.legacy.panels.LabeledItem( + weave.legacy.weave.panels.LabeledItem( item=slurm.nodes().count(), label="Total nodes" ), ], ), ), - weave.legacy.panels.CardTab(name="Nodes", content=slurm.nodes()), - weave.legacy.panels.CardTab(name="Jobs", content=slurm.jobs()), + weave.legacy.weave.panels.CardTab(name="Nodes", content=slurm.nodes()), + weave.legacy.weave.panels.CardTab(name="Jobs", content=slurm.jobs()), ], ) diff --git a/weave/legacy/ecosystem/shap/__init__.py b/weave/legacy/weave/ecosystem/spacy/__init__.py similarity index 51% rename from weave/legacy/ecosystem/shap/__init__.py rename to weave/legacy/weave/ecosystem/spacy/__init__.py index 02c6c8b5027..b0e2199ab0e 100644 --- a/weave/legacy/ecosystem/shap/__init__.py +++ b/weave/legacy/weave/ecosystem/spacy/__init__.py @@ -1,7 +1,7 @@ -from weave.legacy import context_state as _context +from weave.legacy.weave import context_state as _context _loading_builtins_token = _context.set_loading_built_ins() -from weave.legacy.ecosystem.shap.shap import * +from weave.legacy.weave.ecosystem.spacy.spacy import * _context.clear_loading_built_ins(_loading_builtins_token) diff --git a/weave/legacy/ecosystem/spacy/spacy.py b/weave/legacy/weave/ecosystem/spacy/spacy.py similarity index 72% rename from weave/legacy/ecosystem/spacy/spacy.py rename to weave/legacy/weave/ecosystem/spacy/spacy.py index 9dd9823a385..03f5b0fea5e 100644 --- a/weave/legacy/ecosystem/spacy/spacy.py +++ b/weave/legacy/weave/ecosystem/spacy/spacy.py @@ -31,23 +31,23 @@ def spacy(text: str) -> spacy_lib.tokens.doc.Doc: @weave.op() def spacy_doc_dep_to_html( spacy_doc: spacy_lib.tokens.doc.Doc, -) -> weave.legacy.ops.Html: +) -> weave.legacy.weave.ops.Html: from spacy import displacy html = displacy.render( list(spacy_doc.sents), style="dep", jupyter=False, options={"compact": True} ) - return weave.legacy.ops.Html(html) + return weave.legacy.weave.ops.Html(html) @weave.op() def spacy_doc_ent_to_html( spacy_doc: spacy_lib.tokens.doc.Doc, -) -> weave.legacy.ops.Html: +) -> weave.legacy.weave.ops.Html: from spacy import displacy html = displacy.render(spacy_doc, style="ent", jupyter=False) - return weave.legacy.ops.Html(html) + return weave.legacy.weave.ops.Html(html) @weave.type() @@ -56,18 +56,18 @@ class SpacyDocPanel(weave.Panel): input_node: weave.Node[spacy_lib.tokens.doc.Doc] @weave.op() - def render(self) -> weave.legacy.panels.Card: - return weave.legacy.panels.Card( + def render(self) -> weave.legacy.weave.panels.Card: + return weave.legacy.weave.panels.Card( title="Spacy Visualization", subtitle="", content=[ - weave.legacy.panels.CardTab( + weave.legacy.weave.panels.CardTab( name="Dependencies", - content=weave.legacy.panels.PanelHtml(spacy_doc_dep_to_html(self.input_node)), # type: ignore + content=weave.legacy.weave.panels.PanelHtml(spacy_doc_dep_to_html(self.input_node)), # type: ignore ), - weave.legacy.panels.CardTab( + weave.legacy.weave.panels.CardTab( name="Named Entities", - content=weave.legacy.panels.PanelHtml(spacy_doc_ent_to_html(self.input_node)), # type: ignore + content=weave.legacy.weave.panels.PanelHtml(spacy_doc_ent_to_html(self.input_node)), # type: ignore ), ], ) diff --git a/weave/legacy/ecosystem/test_notebook_ecosystem_executions.py b/weave/legacy/weave/ecosystem/test_notebook_ecosystem_executions.py similarity index 98% rename from weave/legacy/ecosystem/test_notebook_ecosystem_executions.py rename to weave/legacy/weave/ecosystem/test_notebook_ecosystem_executions.py index 9d4e9bbfebb..95817ef76f9 100644 --- a/weave/legacy/ecosystem/test_notebook_ecosystem_executions.py +++ b/weave/legacy/weave/ecosystem/test_notebook_ecosystem_executions.py @@ -1,8 +1,8 @@ -from weave.legacy.server import handle_request +from weave.legacy.weave.server import handle_request def test_playback(): - from weave.legacy import ecosystem + from weave.legacy.weave import ecosystem for payload in [execute_payloads[-1]]: res = handle_request(payload, True) diff --git a/weave/legacy/ecosystem/torch_mnist_model_example.py b/weave/legacy/weave/ecosystem/torch_mnist_model_example.py similarity index 93% rename from weave/legacy/ecosystem/torch_mnist_model_example.py rename to weave/legacy/weave/ecosystem/torch_mnist_model_example.py index ab09c6fc70f..8ce668a1ab8 100644 --- a/weave/legacy/ecosystem/torch_mnist_model_example.py +++ b/weave/legacy/weave/ecosystem/torch_mnist_model_example.py @@ -7,8 +7,8 @@ from torchvision import transforms import weave -from weave.legacy import context_state as _context -from weave.legacy.ecosystem import pytorch +from weave.legacy.weave import context_state as _context +from weave.legacy.weave.ecosystem import pytorch DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu") @@ -54,7 +54,7 @@ class Model: output_type=weave.types.List( weave.types.TypedDict( { - "X": weave.legacy.ops.image.PILImageType(), # type: ignore + "X": weave.legacy.weave.ops.image.PILImageType(), # type: ignore "y": weave.types.Int(), } ) @@ -131,7 +131,7 @@ def train_epoch(network, loader, optimizer): @weave.op( render_info={"type": "function"}, input_type={ - "X": weave.types.List(weave.legacy.ops.image.PILImageType()), # type: ignore + "X": weave.types.List(weave.legacy.weave.ops.image.PILImageType()), # type: ignore "y": weave.types.List(weave.types.Int()), # TODO: class enum? }, # TODO: WeaveJS doesn't support callable output type yet. diff --git a/weave/legacy/weave/ecosystem/torchvision/__init__.py b/weave/legacy/weave/ecosystem/torchvision/__init__.py new file mode 100644 index 00000000000..b003b9b82da --- /dev/null +++ b/weave/legacy/weave/ecosystem/torchvision/__init__.py @@ -0,0 +1 @@ +from weave.legacy.weave.ecosystem.torchvision.datasets import * diff --git a/weave/legacy/ecosystem/torchvision/datasets.py b/weave/legacy/weave/ecosystem/torchvision/datasets.py similarity index 98% rename from weave/legacy/ecosystem/torchvision/datasets.py rename to weave/legacy/weave/ecosystem/torchvision/datasets.py index 46f74273f75..b62e5c9a126 100644 --- a/weave/legacy/ecosystem/torchvision/datasets.py +++ b/weave/legacy/weave/ecosystem/torchvision/datasets.py @@ -11,8 +11,8 @@ from torchvision import datasets import weave -from weave.legacy import infer_types -from weave.legacy import panels +from weave.legacy.weave import infer_types +from weave.legacy.weave import panels ExampleType = TypeVar("ExampleType") diff --git a/weave/tests/legacy/__init__.py b/weave/legacy/weave/ecosystem/twitter/__init__.py similarity index 100% rename from weave/tests/legacy/__init__.py rename to weave/legacy/weave/ecosystem/twitter/__init__.py diff --git a/weave/legacy/ecosystem/twitter/tweet.py b/weave/legacy/weave/ecosystem/twitter/tweet.py similarity index 100% rename from weave/legacy/ecosystem/twitter/tweet.py rename to weave/legacy/weave/ecosystem/twitter/tweet.py diff --git a/weave/legacy/ecosystem/example/__init__.py b/weave/legacy/weave/ecosystem/umap/__init__.py similarity index 74% rename from weave/legacy/ecosystem/example/__init__.py rename to weave/legacy/weave/ecosystem/umap/__init__.py index 1ac79cc09e3..e3feb039824 100644 --- a/weave/legacy/ecosystem/example/__init__.py +++ b/weave/legacy/weave/ecosystem/umap/__init__.py @@ -2,13 +2,13 @@ import logging -from weave.legacy import context_state +from weave.legacy.weave import context_state logging.getLogger("ecosystem_example").setLevel(logging.ERROR) loading_builtins_token = context_state.set_loading_built_ins() try: - from weave.legacy.ecosystem.example import ops + from weave.legacy.weave.ecosystem.umap.ops import * finally: context_state.clear_loading_built_ins(loading_builtins_token) diff --git a/weave/legacy/ecosystem/umap/ops.py b/weave/legacy/weave/ecosystem/umap/ops.py similarity index 96% rename from weave/legacy/ecosystem/umap/ops.py rename to weave/legacy/weave/ecosystem/umap/ops.py index 74951e7b7c4..893cba22ecb 100644 --- a/weave/legacy/ecosystem/umap/ops.py +++ b/weave/legacy/weave/ecosystem/umap/ops.py @@ -11,7 +11,7 @@ ) import weave -from weave.legacy import ops_arrow +from weave.legacy.weave import ops_arrow UMAP_LIB = None diff --git a/weave/legacy/weave/ecosystem/wandb/__init__.py b/weave/legacy/weave/ecosystem/wandb/__init__.py new file mode 100644 index 00000000000..9786a4f2489 --- /dev/null +++ b/weave/legacy/weave/ecosystem/wandb/__init__.py @@ -0,0 +1,13 @@ +from weave.legacy.weave import context_state as _context + +_loading_builtins_token = _context.set_loading_built_ins() + +from weave.legacy.weave.ecosystem.wandb import wandb_objs +from weave.legacy.weave.ecosystem.wandb.panel_distribution import * +from weave.legacy.weave.ecosystem.wandb.panel_geo import * +from weave.legacy.weave.ecosystem.wandb.panel_scatter import * +from weave.legacy.weave.ecosystem.wandb.panel_time_series import * +from weave.legacy.weave.ecosystem.wandb.run_chain import * +from weave.legacy.weave.ecosystem.wandb.weave_plotly import * + +_context.clear_loading_built_ins(_loading_builtins_token) diff --git a/weave/legacy/ecosystem/wandb/gql_artifact_dag.py b/weave/legacy/weave/ecosystem/wandb/gql_artifact_dag.py similarity index 98% rename from weave/legacy/ecosystem/wandb/gql_artifact_dag.py rename to weave/legacy/weave/ecosystem/wandb/gql_artifact_dag.py index 4fecc2909e7..ca22e0b1801 100644 --- a/weave/legacy/ecosystem/wandb/gql_artifact_dag.py +++ b/weave/legacy/weave/ecosystem/wandb/gql_artifact_dag.py @@ -1,6 +1,6 @@ import gql -from weave.legacy import wandb_api +from weave.legacy.weave import wandb_api ARTIFACT_LINEAGE_QUERY = gql.gql( """ diff --git a/weave/legacy/ecosystem/wandb/panel_distribution.py b/weave/legacy/weave/ecosystem/wandb/panel_distribution.py similarity index 75% rename from weave/legacy/ecosystem/wandb/panel_distribution.py rename to weave/legacy/weave/ecosystem/wandb/panel_distribution.py index 3ed85e247e3..387db6ece06 100644 --- a/weave/legacy/ecosystem/wandb/panel_distribution.py +++ b/weave/legacy/weave/ecosystem/wandb/panel_distribution.py @@ -2,9 +2,9 @@ import typing import weave -from weave import weave_internal -from weave.legacy import panel_util -from weave.legacy.ecosystem.wandb import weave_plotly +from weave.legacy.weave import weave_internal +from weave.legacy.weave import panel_util +from weave.legacy.weave.ecosystem.wandb import weave_plotly @weave.type() @@ -14,14 +14,14 @@ class DistributionConfig: # We should make a better type to represent this, so it can be # distinguished from an expression like bin_size. value_fn: weave.Node[typing.Any] = dataclasses.field( - default_factory=lambda: weave.legacy.graph.VoidNode() + default_factory=lambda: weave.legacy.weave.graph.VoidNode() ) label_fn: weave.Node[typing.Any] = dataclasses.field( - default_factory=lambda: weave.legacy.graph.VoidNode() + default_factory=lambda: weave.legacy.weave.graph.VoidNode() ) # This is an expression. It will be stored in the config as Node. bin_size: weave.Node[typing.Any] = dataclasses.field( - default_factory=lambda: weave.legacy.graph.VoidNode() + default_factory=lambda: weave.legacy.weave.graph.VoidNode() ) @@ -35,7 +35,7 @@ class Distribution(weave.Panel): @weave.op() def initialize(self) -> DistributionConfig: input_node = self.input_node - unnested = weave.legacy.ops.unnest(input_node) + unnested = weave.legacy.weave.ops.unnest(input_node) return DistributionConfig( value_fn=weave_internal.define_fn( {"item": unnested.type.object_type}, @@ -50,19 +50,19 @@ def initialize(self) -> DistributionConfig: ) @weave.op() - def render_config(self) -> weave.legacy.panels.Group: + def render_config(self) -> weave.legacy.weave.panels.Group: config = typing.cast(DistributionConfig, self.config) - return weave.legacy.panels.Group( + return weave.legacy.weave.panels.Group( items={ - "value_fn": weave.legacy.panels.LabeledItem( + "value_fn": weave.legacy.weave.panels.LabeledItem( label="value", - item=weave.legacy.panels.FunctionEditor(config.value_fn), + item=weave.legacy.weave.panels.FunctionEditor(config.value_fn), ), - "label_fn": weave.legacy.panels.LabeledItem( + "label_fn": weave.legacy.weave.panels.LabeledItem( label="label", - item=weave.legacy.panels.FunctionEditor(config.label_fn), + item=weave.legacy.weave.panels.FunctionEditor(config.label_fn), ), - "bin_size": weave.legacy.panels.LabeledItem( + "bin_size": weave.legacy.weave.panels.LabeledItem( label="bin_size", # Must execute here because bin_size is an expression. # Editor panels take the actual type they edit (in this @@ -70,7 +70,7 @@ def render_config(self) -> weave.legacy.panels.Group: # const node directly in the config, or if the expression # refers to variables, the edit will be routed to the appropriate # owner. - item=weave.legacy.panels.Slider(config.bin_size.execute()), # type: ignore + item=weave.legacy.weave.panels.Slider(config.bin_size.execute()), # type: ignore ), } ) @@ -87,10 +87,10 @@ def render(self) -> weave_plotly.PanelPlotly: config.value_fn.type.output_type # type: ignore ): # TODO: need a nicer way to return error states - return weave.legacy.panels.PanelString("Invalid value_fn") # type: ignore + return weave.legacy.weave.panels.PanelString("Invalid value_fn") # type: ignore # We always unnest, so that we can compare across groups of items # easily. (the Distribution notebook) - unnested = weave.legacy.ops.unnest(input_node) + unnested = weave.legacy.weave.ops.unnest(input_node) bin_size = config.bin_size def bin_func(item): @@ -111,10 +111,10 @@ def bin_func(item): else: group_items["label"] = config.label_fn(item) - return weave.legacy.ops.dict_(**group_items) + return weave.legacy.weave.ops.dict_(**group_items) binned = unnested.groupby(lambda item: bin_func(item)).map( - lambda group: weave.legacy.ops.dict_( + lambda group: weave.legacy.weave.ops.dict_( value=group.groupkey()["value"], label=group.groupkey()["label"], count=group.count(), @@ -128,9 +128,9 @@ def bin_func(item): @weave.op() def distribution_panel_plot_render( input_node: weave.Node[list[typing.Any]], config: DistributionConfig -) -> weave.legacy.panels.Plot: - unnested = weave.legacy.ops.unnest(input_node) - bin_size = weave.legacy.ops.execute(config.bin_size) +) -> weave.legacy.weave.panels.Plot: + unnested = weave.legacy.weave.ops.unnest(input_node) + bin_size = weave.legacy.weave.ops.execute(config.bin_size) def bin_func(item): value_fn_output_type = config.value_fn.type.output_type @@ -146,18 +146,18 @@ def bin_func(item): else: group_items["label"] = config.label_fn(item) - res = weave.legacy.ops.dict_(**group_items) + res = weave.legacy.weave.ops.dict_(**group_items) return res binned = unnested.groupby(lambda item: bin_func(item)).map( - lambda group: weave.legacy.ops.dict_( + lambda group: weave.legacy.weave.ops.dict_( value=group.groupkey()["value"], label=group.groupkey()["label"], count=group.count(), ) ) - return weave.legacy.panels.Plot( + return weave.legacy.weave.panels.Plot( binned, x=lambda row: row["value"], y=lambda row: row["count"], diff --git a/weave/legacy/ecosystem/wandb/panel_geo.py b/weave/legacy/weave/ecosystem/wandb/panel_geo.py similarity index 77% rename from weave/legacy/ecosystem/wandb/panel_geo.py rename to weave/legacy/weave/ecosystem/wandb/panel_geo.py index be1baff8a1f..15c1f1c479d 100644 --- a/weave/legacy/ecosystem/wandb/panel_geo.py +++ b/weave/legacy/weave/ecosystem/wandb/panel_geo.py @@ -3,8 +3,8 @@ import typing import weave -from weave import weave_internal -from weave.legacy.ecosystem.wandb import weave_plotly +from weave.legacy.weave import weave_internal +from weave.legacy.weave.ecosystem.wandb import weave_plotly # This is the panel's config (the state that is stored in the panel and configurable @@ -12,13 +12,13 @@ @weave.type() class GeoConfig: x_fn: weave.Node[float] = dataclasses.field( - default_factory=lambda: weave.legacy.graph.VoidNode() + default_factory=lambda: weave.legacy.weave.graph.VoidNode() ) y_fn: weave.Node[float] = dataclasses.field( - default_factory=lambda: weave.legacy.graph.VoidNode() + default_factory=lambda: weave.legacy.weave.graph.VoidNode() ) color_fn: weave.Node[float] = dataclasses.field( - default_factory=lambda: weave.legacy.graph.VoidNode() + default_factory=lambda: weave.legacy.weave.graph.VoidNode() ) @@ -69,10 +69,10 @@ def geo_default_config( def geo( input_node: weave.Node[list[typing.Any]], config: GeoConfig ) -> weave_plotly.PanelPlotly: - unnested = weave.legacy.ops.unnest(input_node) + unnested = weave.legacy.weave.ops.unnest(input_node) config = geo_default_config(config, unnested) plot_data = unnested.map( - lambda item: weave.legacy.ops.dict_( + lambda item: weave.legacy.weave.ops.dict_( long=config.x_fn(item), # type: ignore lat=config.y_fn(item), # type: ignore color=config.color_fn(item), # type: ignore @@ -86,27 +86,27 @@ def geo( @weave.op(name="Geo_config") def geo_config( input_node: weave.Node[list[typing.Any]], config: GeoConfig -) -> weave.legacy.panels.Group: - unnested = weave.legacy.ops.unnest(input_node) +) -> weave.legacy.weave.panels.Group: + unnested = weave.legacy.weave.ops.unnest(input_node) config = geo_default_config(config, unnested) - return weave.legacy.panels.Group( + return weave.legacy.weave.panels.Group( items={ - "x_fn": weave.legacy.panels.LabeledItem( + "x_fn": weave.legacy.weave.panels.LabeledItem( label="x", - item=weave.legacy.panels.FunctionEditor( - config=weave.legacy.panels.FunctionEditorConfig(config.x_fn) + item=weave.legacy.weave.panels.FunctionEditor( + config=weave.legacy.weave.panels.FunctionEditorConfig(config.x_fn) ), ), - "y_fn": weave.legacy.panels.LabeledItem( + "y_fn": weave.legacy.weave.panels.LabeledItem( label="y", - item=weave.legacy.panels.FunctionEditor( - config=weave.legacy.panels.FunctionEditorConfig(config.y_fn) + item=weave.legacy.weave.panels.FunctionEditor( + config=weave.legacy.weave.panels.FunctionEditorConfig(config.y_fn) ), ), - "color_fn": weave.legacy.panels.LabeledItem( + "color_fn": weave.legacy.weave.panels.LabeledItem( label="color", - item=weave.legacy.panels.FunctionEditor( - config=weave.legacy.panels.FunctionEditorConfig(config.color_fn) + item=weave.legacy.weave.panels.FunctionEditor( + config=weave.legacy.weave.panels.FunctionEditorConfig(config.color_fn) ), ), } @@ -134,7 +134,7 @@ def __init__( if self.config is None: self.config = GeoConfig() - unnested = weave.legacy.ops.unnest(self.input_node) + unnested = weave.legacy.weave.ops.unnest(self.input_node) if "x_fn" in options: sig = inspect.signature(options["x_fn"]) param_name = list(sig.parameters.values())[0].name @@ -171,28 +171,28 @@ def __init__( @weave.op(output_type=lambda input_type: input_type["self"].input_node.output_type) def selected(self): # TODO: This function is not right! We need to do a range selection in polar space! - unnested = weave.legacy.ops.unnest(self.input_node) + unnested = weave.legacy.weave.ops.unnest(self.input_node) config = geo_default_config(self.config, unnested) filtered = unnested.filter( - lambda item: weave.legacy.ops.Boolean.bool_and( - weave.legacy.ops.Boolean.bool_and( - weave.legacy.ops.Boolean.bool_and( + lambda item: weave.legacy.weave.ops.Boolean.bool_and( + weave.legacy.weave.ops.Boolean.bool_and( + weave.legacy.weave.ops.Boolean.bool_and( config.x_fn(item) - > weave.legacy.ops.TypedDict.pick( + > weave.legacy.weave.ops.TypedDict.pick( self._renderAsPanel.config.selected, "xMin" ), config.x_fn(item) - < weave.legacy.ops.TypedDict.pick( + < weave.legacy.weave.ops.TypedDict.pick( self._renderAsPanel.config.selected, "xMax" ), ), config.y_fn(item) - > weave.legacy.ops.TypedDict.pick( + > weave.legacy.weave.ops.TypedDict.pick( self._renderAsPanel.config.selected, "yMin" ), ), config.y_fn(item) - < weave.legacy.ops.TypedDict.pick( + < weave.legacy.weave.ops.TypedDict.pick( self._renderAsPanel.config.selected, "yMax" ), ) diff --git a/weave/legacy/ecosystem/wandb/panel_scatter.py b/weave/legacy/weave/ecosystem/wandb/panel_scatter.py similarity index 65% rename from weave/legacy/ecosystem/wandb/panel_scatter.py rename to weave/legacy/weave/ecosystem/wandb/panel_scatter.py index 4b86e22edb1..f670ae23c48 100644 --- a/weave/legacy/ecosystem/wandb/panel_scatter.py +++ b/weave/legacy/weave/ecosystem/wandb/panel_scatter.py @@ -3,20 +3,20 @@ import typing import weave -from weave import weave_internal -from weave.legacy.ecosystem.wandb import weave_plotly +from weave.legacy.weave import weave_internal +from weave.legacy.weave.ecosystem.wandb import weave_plotly @weave.type() class ScatterConfig: x_fn: weave.Node[typing.Optional[typing.Any]] = dataclasses.field( - default_factory=lambda: weave.legacy.graph.VoidNode() + default_factory=lambda: weave.legacy.weave.graph.VoidNode() ) y_fn: weave.Node[typing.Optional[typing.Any]] = dataclasses.field( - default_factory=lambda: weave.legacy.graph.VoidNode() + default_factory=lambda: weave.legacy.weave.graph.VoidNode() ) label_fn: weave.Node[typing.Any] = dataclasses.field( - default_factory=lambda: weave.legacy.graph.VoidNode() + default_factory=lambda: weave.legacy.weave.graph.VoidNode() ) @@ -30,7 +30,7 @@ class Scatter(weave.Panel): @weave.op() def initialize(self) -> ScatterConfig: input_node = self.input_node - unnested = weave.legacy.ops.unnest(input_node) + unnested = weave.legacy.weave.ops.unnest(input_node) return ScatterConfig( x_fn=weave_internal.define_fn( {"item": unnested.type.object_type}, @@ -48,19 +48,19 @@ def initialize(self) -> ScatterConfig: # The config render op. This renders the config editor. @weave.op() - def render_config(self) -> weave.legacy.panels.Group: + def render_config(self) -> weave.legacy.weave.panels.Group: config = typing.cast(ScatterConfig, self.config) - return weave.legacy.panels.Group( + return weave.legacy.weave.panels.Group( items={ - "x_fn": weave.legacy.panels.LabeledItem( - label="x", item=weave.legacy.panels.FunctionEditor(config.x_fn) + "x_fn": weave.legacy.weave.panels.LabeledItem( + label="x", item=weave.legacy.weave.panels.FunctionEditor(config.x_fn) ), - "y_fn": weave.legacy.panels.LabeledItem( - label="y", item=weave.legacy.panels.FunctionEditor(config.y_fn) + "y_fn": weave.legacy.weave.panels.LabeledItem( + label="y", item=weave.legacy.weave.panels.FunctionEditor(config.y_fn) ), - "label_fn": weave.legacy.panels.LabeledItem( + "label_fn": weave.legacy.weave.panels.LabeledItem( label="label", - item=weave.legacy.panels.FunctionEditor(config.label_fn), + item=weave.legacy.weave.panels.FunctionEditor(config.label_fn), ), } ) @@ -70,7 +70,7 @@ def render_config(self) -> weave.legacy.panels.Group: def render(self) -> weave_plotly.PanelPlotly: input_node = self.input_node config = typing.cast(ScatterConfig, self.config) - unnested = weave.legacy.ops.unnest(input_node) + unnested = weave.legacy.weave.ops.unnest(input_node) if ( not weave.types.optional(weave.types.Float()).assign_type(config.x_fn.type) or not weave.types.optional(weave.types.Float()).assign_type( @@ -80,14 +80,14 @@ def render(self) -> weave_plotly.PanelPlotly: config.label_fn.type ) ): - return weave.legacy.panels.PanelHtml(weave.legacy.ops.Html("No data")) # type: ignore + return weave.legacy.weave.panels.PanelHtml(weave.legacy.weave.ops.Html("No data")) # type: ignore if config.label_fn.type == weave.types.Invalid(): plot_data = unnested.map( - lambda item: weave.legacy.ops.dict_(x=config.x_fn(item), y=config.y_fn(item)) # type: ignore + lambda item: weave.legacy.weave.ops.dict_(x=config.x_fn(item), y=config.y_fn(item)) # type: ignore ) else: plot_data = unnested.map( - lambda item: weave.legacy.ops.dict_( + lambda item: weave.legacy.weave.ops.dict_( x=config.x_fn(item), y=config.y_fn(item), label=config.label_fn(item), @@ -100,28 +100,28 @@ def render(self) -> weave_plotly.PanelPlotly: # TODO: Fix @weave.op(output_type=lambda input_type: input_type["self"].input_node.output_type) def selected(self): - unnested = weave.legacy.ops.unnest(self.input_node) + unnested = weave.legacy.weave.ops.unnest(self.input_node) config = self.config filtered = unnested.filter( - lambda item: weave.legacy.ops.Boolean.bool_and( - weave.legacy.ops.Boolean.bool_and( - weave.legacy.ops.Boolean.bool_and( + lambda item: weave.legacy.weave.ops.Boolean.bool_and( + weave.legacy.weave.ops.Boolean.bool_and( + weave.legacy.weave.ops.Boolean.bool_and( config.x_fn(item) - > weave.legacy.ops.TypedDict.pick( + > weave.legacy.weave.ops.TypedDict.pick( self._renderAsPanel.config.selected, "xMin" ), config.x_fn(item) - < weave.legacy.ops.TypedDict.pick( + < weave.legacy.weave.ops.TypedDict.pick( self._renderAsPanel.config.selected, "xMax" ), ), config.y_fn(item) - > weave.legacy.ops.TypedDict.pick( + > weave.legacy.weave.ops.TypedDict.pick( self._renderAsPanel.config.selected, "yMin" ), ), config.y_fn(item) - < weave.legacy.ops.TypedDict.pick( + < weave.legacy.weave.ops.TypedDict.pick( self._renderAsPanel.config.selected, "yMax" ), ) diff --git a/weave/legacy/ecosystem/wandb/panel_time_series.py b/weave/legacy/weave/ecosystem/wandb/panel_time_series.py similarity index 80% rename from weave/legacy/ecosystem/wandb/panel_time_series.py rename to weave/legacy/weave/ecosystem/wandb/panel_time_series.py index a95069bc279..406ff8f140d 100644 --- a/weave/legacy/ecosystem/wandb/panel_time_series.py +++ b/weave/legacy/weave/ecosystem/wandb/panel_time_series.py @@ -2,10 +2,10 @@ import typing import weave -from weave import weave_internal -from weave.legacy.ecosystem.wandb import weave_plotly -from weave.legacy.language_features.tagging import tagged_value_type -from weave.legacy.panel_util import make_node +from weave.legacy.weave import weave_internal +from weave.legacy.weave.ecosystem.wandb import weave_plotly +from weave.legacy.weave.language_features.tagging import tagged_value_type +from weave.legacy.weave.panel_util import make_node TIME_SERIES_BIN_SIZES_SEC = [ # TODO: will need more steps along here for smooth zooming. @@ -53,27 +53,27 @@ @weave.type() class TimeSeriesConfig: x: weave.Node[typing.Any] = dataclasses.field( - default_factory=lambda: weave.legacy.graph.VoidNode() + default_factory=lambda: weave.legacy.weave.graph.VoidNode() ) agg: weave.Node[typing.Any] = dataclasses.field( - default_factory=lambda: weave.legacy.graph.VoidNode() + default_factory=lambda: weave.legacy.weave.graph.VoidNode() ) min_x: weave.Node[typing.Any] = dataclasses.field( - default_factory=lambda: weave.legacy.graph.VoidNode() + default_factory=lambda: weave.legacy.weave.graph.VoidNode() ) max_x: weave.Node[typing.Any] = dataclasses.field( - default_factory=lambda: weave.legacy.graph.VoidNode() + default_factory=lambda: weave.legacy.weave.graph.VoidNode() ) label: weave.Node[ typing.Optional[typing.Union[str, weave.types.InvalidPy]] - ] = dataclasses.field(default_factory=lambda: weave.legacy.graph.VoidNode()) + ] = dataclasses.field(default_factory=lambda: weave.legacy.weave.graph.VoidNode()) mark: weave.Node[str] = dataclasses.field( - default_factory=lambda: weave.legacy.graph.ConstNode( + default_factory=lambda: weave.legacy.weave.graph.ConstNode( weave.types.String(), "bar" ) ) axis_labels: weave.Node[dict[str, str]] = dataclasses.field( - default_factory=lambda: weave.legacy.graph.ConstNode( + default_factory=lambda: weave.legacy.weave.graph.ConstNode( weave.types.Dict(weave.types.String(), weave.types.String()), {}, ) @@ -83,7 +83,7 @@ class TimeSeriesConfig: def first_column_of_type( node_type: weave.types.Type, desired_type: weave.types.Type, -) -> typing.Tuple[weave.legacy.graph.ConstNode, weave.legacy.graph.ConstNode]: +) -> typing.Tuple[weave.legacy.weave.graph.ConstNode, weave.legacy.weave.graph.ConstNode]: if isinstance(node_type, tagged_value_type.TaggedValueType): node_type = node_type.value if weave.types.List().assign_type(node_type): @@ -119,8 +119,8 @@ def first_column_of_type( {"item": object_type}, lambda item: item[key] ) # return weave_internal.define_fn( - # {"input_node": node_type}, weave.legacy.graph.VoidNode() - # ), weave_internal.define_fn({"item": object_type}, lambda _: weave.legacy.graph.VoidNode()) + # {"input_node": node_type}, weave.legacy.weave.graph.VoidNode() + # ), weave_internal.define_fn({"item": object_type}, lambda _: weave.legacy.weave.graph.VoidNode()) raise ValueError( f"Can't extract column with type {desired_type} from node of type {node_type}" ) @@ -160,7 +160,7 @@ def __init__(self, input_node, vars=None, config=None, **options): super().__init__(input_node=input_node, vars=vars) self.config = config - unnested = weave.legacy.ops.unnest(input_node) + unnested = weave.legacy.weave.ops.unnest(input_node) # TODO: add the ability to configure options here if self.config is None: @@ -168,7 +168,7 @@ def __init__(self, input_node, vars=None, config=None, **options): for attr in ["x", "min_x", "max_x", "label", "mark", "agg", "axis_labels"]: if attr in options: value = options[attr] - if not isinstance(value, weave.legacy.graph.Node): + if not isinstance(value, weave.legacy.weave.graph.Node): if attr in ["min_x", "max_x", "mark", "axis_labels"]: value = make_node(value) if attr in ["min_x", "max_x"]: @@ -186,7 +186,7 @@ def __init__(self, input_node, vars=None, config=None, **options): else: value = weave_internal.define_fn( {"item": unnested.type.object_type}, - lambda item: weave.legacy.graph.VoidNode(), + lambda item: weave.legacy.weave.graph.VoidNode(), ) setattr(self.config, attr, value) @@ -239,32 +239,32 @@ def initialize(self) -> TimeSeriesConfig: # The config render op. This renders the config editor. @weave.op() - def render_config(self) -> weave.legacy.panels.Group: + def render_config(self) -> weave.legacy.weave.panels.Group: input_node = self.input_node config = typing.cast(TimeSeriesConfig, self.config) - return weave.legacy.panels.Group( + return weave.legacy.weave.panels.Group( items={ - "x": weave.legacy.panels.LabeledItem( - label="x", item=weave.legacy.panels.FunctionEditor(config.x) + "x": weave.legacy.weave.panels.LabeledItem( + label="x", item=weave.legacy.weave.panels.FunctionEditor(config.x) ), - "label": weave.legacy.panels.LabeledItem( + "label": weave.legacy.weave.panels.LabeledItem( label="label", - item=weave.legacy.panels.FunctionEditor(config.label), + item=weave.legacy.weave.panels.FunctionEditor(config.label), ), - "min_x": weave.legacy.panels.LabeledItem( + "min_x": weave.legacy.weave.panels.LabeledItem( label="min_x", - item=weave.legacy.panels.FunctionEditor(config.min_x), + item=weave.legacy.weave.panels.FunctionEditor(config.min_x), ), - "max_x": weave.legacy.panels.LabeledItem( + "max_x": weave.legacy.weave.panels.LabeledItem( label="max_x", - item=weave.legacy.panels.FunctionEditor(config.max_x), + item=weave.legacy.weave.panels.FunctionEditor(config.max_x), ), - "agg": weave.legacy.panels.LabeledItem( - label="agg", item=weave.legacy.panels.FunctionEditor(config.agg) + "agg": weave.legacy.weave.panels.LabeledItem( + label="agg", item=weave.legacy.weave.panels.FunctionEditor(config.agg) ), - "mark": weave.legacy.panels.LabeledItem( + "mark": weave.legacy.weave.panels.LabeledItem( label="mark", - item=weave.legacy.panels.ObjectPicker( + item=weave.legacy.weave.panels.ObjectPicker( weave_internal.make_const_node( weave.types.List(weave.types.String()), [ @@ -273,7 +273,7 @@ def render_config(self) -> weave.legacy.panels.Group: "point", ], ), - config=weave.legacy.panels.ObjectPickerConfig( + config=weave.legacy.weave.panels.ObjectPickerConfig( choice=config.mark ), ), @@ -296,13 +296,13 @@ def render(self) -> weave_plotly.PanelPlotly: if not weave.types.optional(weave.types.Timestamp()).assign_type( min_x.type ) or not weave.types.optional(weave.types.Timestamp()).assign_type(max_x.type): - return weave.legacy.panels.PanelHtml(weave.legacy.ops.Html("No data")) # type: ignore + return weave.legacy.weave.panels.PanelHtml(weave.legacy.weave.ops.Html("No data")) # type: ignore exact_bin_size = ((max_x - min_x) / N_BINS).totalSeconds() # type: ignore bin_size_index = TIME_SERIES_BIN_SIZES_SEC_NODE.map( # type: ignore lambda x: ( (x - exact_bin_size).abs() - / weave.legacy.ops.make_list(a=x, b=exact_bin_size).min() + / weave.legacy.weave.ops.make_list(a=x, b=exact_bin_size).min() ) # lambda x: (x / exact_bin_size - 1).abs() # original ).argmin() @@ -323,25 +323,25 @@ def bin_fn(item): bin_start = bin_start_ms bin_end = bin_end_ms - group_items["bin"] = weave.legacy.ops.dict_(start=bin_start, stop=bin_end) + group_items["bin"] = weave.legacy.weave.ops.dict_(start=bin_start, stop=bin_end) if label_fn_output_type == weave.types.Invalid(): group_items["label"] = "no_label" else: group_items["label"] = config.label(item) - return weave.legacy.ops.dict_(**group_items) + return weave.legacy.weave.ops.dict_(**group_items) binned = ( unnested.filter( - lambda item: weave.legacy.ops.Boolean.bool_and( + lambda item: weave.legacy.weave.ops.Boolean.bool_and( config.x(item) <= max_x, config.x(item) >= min_x, # type: ignore ) ) .groupby(lambda item: bin_fn(item)) .map( - lambda group: weave.legacy.ops.dict_( + lambda group: weave.legacy.weave.ops.dict_( x=group.groupkey()["bin"], label=group.groupkey()["label"], y=config.agg(group), # type: ignore @@ -349,12 +349,12 @@ def bin_fn(item): ) # this is needed because otherwise the lines look like a scrambled mess .sort( - lambda item: weave.legacy.ops.make_list(a=item["x"]["start"]), + lambda item: weave.legacy.weave.ops.make_list(a=item["x"]["start"]), ["asc"], ) ) - default_labels = weave.legacy.ops.dict_( + default_labels = weave.legacy.weave.ops.dict_( # x=function_to_string(config.x), # y=function_to_string(config.agg), # label=function_to_string(config.label), diff --git a/weave/legacy/ecosystem/wandb/run_chain.py b/weave/legacy/weave/ecosystem/wandb/run_chain.py similarity index 91% rename from weave/legacy/ecosystem/wandb/run_chain.py rename to weave/legacy/weave/ecosystem/wandb/run_chain.py index e57a79da9ff..3283b1742b8 100644 --- a/weave/legacy/ecosystem/wandb/run_chain.py +++ b/weave/legacy/weave/ecosystem/wandb/run_chain.py @@ -2,8 +2,8 @@ import typing import weave -from weave.legacy import compile, op_def, ops_arrow -from weave.legacy.ecosystem.wandb import gql_artifact_dag +from weave.legacy.weave import compile, op_def, ops_arrow +from weave.legacy.weave.ecosystem.wandb import gql_artifact_dag @weave.type() @@ -20,7 +20,7 @@ class RunChain: def _history_node(self): with op_def.no_refine(): - proj = weave.legacy.ops.project(self.entity_name, self.project_name) + proj = weave.legacy.weave.ops.project(self.entity_name, self.project_name) history_nodes = [] for seg in self.segments: # This is the only use of the deprecated history2 op in the Weave @@ -32,8 +32,8 @@ def _history_node(self): history_nodes.append(hist_node) - history_node = weave.legacy.ops.List.concat( - weave.legacy.ops.make_list( + history_node = weave.legacy.weave.ops.List.concat( + weave.legacy.weave.ops.make_list( **{f"node{i}": n for i, n in enumerate(history_nodes)} ) ) @@ -56,7 +56,7 @@ def history(self): def run_chain(run_path: str) -> RunChain: with op_def.no_refine(): entity, project, run_id = run_path.split("/") - proj = weave.legacy.ops.project(entity, project) + proj = weave.legacy.weave.ops.project(entity, project) run = proj.run(run_id) # Get the checkpoint artifact for this one if one exists diff --git a/weave/legacy/ecosystem/wandb/test_panel_distribution.py b/weave/legacy/weave/ecosystem/wandb/test_panel_distribution.py similarity index 93% rename from weave/legacy/ecosystem/wandb/test_panel_distribution.py rename to weave/legacy/weave/ecosystem/wandb/test_panel_distribution.py index 71090b2d59c..17b5a7400f8 100644 --- a/weave/legacy/ecosystem/wandb/test_panel_distribution.py +++ b/weave/legacy/weave/ecosystem/wandb/test_panel_distribution.py @@ -1,8 +1,8 @@ import random import weave -from weave import weave_internal -from weave.legacy.ecosystem.wandb import panel_distribution +from weave.legacy.weave import weave_internal +from weave.legacy.weave.ecosystem.wandb import panel_distribution def test_flow(): diff --git a/weave/legacy/ecosystem/wandb/wandb_objs.py b/weave/legacy/weave/ecosystem/wandb/wandb_objs.py similarity index 73% rename from weave/legacy/ecosystem/wandb/wandb_objs.py rename to weave/legacy/weave/ecosystem/wandb/wandb_objs.py index 449d82ed786..38731a5af83 100644 --- a/weave/legacy/ecosystem/wandb/wandb_objs.py +++ b/weave/legacy/weave/ecosystem/wandb/wandb_objs.py @@ -1,8 +1,8 @@ import typing import weave -from weave.legacy import registry_mem -from weave.legacy.ops_domain import run_ops, wb_domain_types +from weave.legacy.weave import registry_mem +from weave.legacy.weave.ops_domain import run_ops, wb_domain_types # We can't chain ops called .name() because of a weird bug :( [its a field on VarNode]. # So we have to get the ops here and call them directly for now. @@ -23,7 +23,7 @@ def org_model(entity_name: str, model_name: str) -> FakeWandbModel: return FakeWandbModel(model_name) -GHOSTWRITE_MD = weave.legacy.ops.Markdown( +GHOSTWRITE_MD = weave.legacy.weave.ops.Markdown( """ # [ghostwrite.ai](https://ghostwrite.ai). @@ -35,19 +35,19 @@ def org_model(entity_name: str, model_name: str) -> FakeWandbModel: @weave.op() def fakewandbmodel_render( model_node: weave.Node[FakeWandbModel], -) -> weave.legacy.panels.Card: +) -> weave.legacy.weave.panels.Card: model = typing.cast(FakeWandbModel, model_node) - return weave.legacy.panels.Card( + return weave.legacy.weave.panels.Card( title=model.name, subtitle="", content=[ - weave.legacy.panels.CardTab( + weave.legacy.weave.panels.CardTab( name="Description", - content=weave.legacy.panels.PanelMarkdown(GHOSTWRITE_MD), # type: ignore + content=weave.legacy.weave.panels.PanelMarkdown(GHOSTWRITE_MD), # type: ignore ), - weave.legacy.panels.CardTab( + weave.legacy.weave.panels.CardTab( name="Predictions", - content=weave.legacy.ops.project("shawn", "ghostwrite-test1") + content=weave.legacy.weave.ops.project("shawn", "ghostwrite-test1") .runs() .summary()["predictions"] .table() @@ -62,39 +62,39 @@ def fakewandbmodel_render( @weave.op() def entity_render( entity_node: weave.Node[wb_domain_types.Entity], -) -> weave.legacy.panels.Card: +) -> weave.legacy.weave.panels.Card: entity = typing.cast(wb_domain_types.Entity, entity_node) - return weave.legacy.panels.Card( + return weave.legacy.weave.panels.Card( title=entity_name_op(entity), subtitle="", content=[ - weave.legacy.panels.CardTab( + weave.legacy.weave.panels.CardTab( name="Projects", - content=weave.legacy.panels.Table( + content=weave.legacy.weave.panels.Table( entity.projects(), # type: ignore columns=[ - lambda project: weave.legacy.panels.WeaveLink( + lambda project: weave.legacy.weave.panels.WeaveLink( project_name_op(project), vars={ "entity_name": entity_name_op(project.entity()), "project_name": project_name_op(project), }, - to=lambda input, vars: weave.legacy.ops.project( + to=lambda input, vars: weave.legacy.weave.ops.project( vars["entity_name"], vars["project_name"] ), ), ], ), ), - weave.legacy.panels.CardTab( + weave.legacy.weave.panels.CardTab( name="Registered Models", - content=weave.legacy.panels.Table( + content=weave.legacy.weave.panels.Table( weave.save( ["ghostwrite", "credit card predictor", "stopsigns3"], name="model_list", ), columns=[ - lambda model_name: weave.legacy.panels.WeaveLink( + lambda model_name: weave.legacy.weave.panels.WeaveLink( model_name, vars={ "entity_name": entity_name_op(entity), @@ -116,18 +116,18 @@ class ProjectRunsTable(weave.Panel): input_node: weave.Node[list[wb_domain_types.Run]] @weave.op() - def render(self) -> weave.legacy.panels.Table: - return weave.legacy.panels.Table( + def render(self) -> weave.legacy.weave.panels.Table: + return weave.legacy.weave.panels.Table( self.input_node, columns=[ - lambda run: weave.legacy.panels.WeaveLink( + lambda run: weave.legacy.weave.panels.WeaveLink( run.id(), vars={ "entity_name": entity_name_op(run.project().entity()), "project_name": project_name_op(run.project()), "run_id": run.id(), }, - to=lambda input, vars: weave.legacy.ops.project( + to=lambda input, vars: weave.legacy.weave.ops.project( vars["entity_name"], vars["project_name"] ).run(vars["run_id"]), ), @@ -143,28 +143,28 @@ class ProjectArtifactsTable(weave.Panel): input_node: weave.Node[list[wb_domain_types.ArtifactCollection]] @weave.op() - def render(self) -> weave.legacy.panels.Table: + def render(self) -> weave.legacy.weave.panels.Table: # This breaks if there is a variable in the node # types_names = weave.use(artifacts._get_op("type")().name().unique()) - # return weave.legacy.panels.Card( + # return weave.legacy.weave.panels.Card( # title="Artifacts", # subtitle="", # content=[ - # weave.legacy.panels.CardTab(name=type_name, content=[type_name]) + # weave.legacy.weave.panels.CardTab(name=type_name, content=[type_name]) # for type_name in types_names # ], # ) - return weave.legacy.panels.Table( + return weave.legacy.weave.panels.Table( self.input_node, columns=[ - lambda artifact: weave.legacy.panels.WeaveLink( + lambda artifact: weave.legacy.weave.panels.WeaveLink( artifact._get_op("name")(), vars={ "entity_name": entity_name_op(artifact.project().entity()), "project_name": project_name_op(artifact.project()), "artifact_name": artifact._get_op("name")(), }, - to=lambda input, vars: weave.legacy.ops.project( + to=lambda input, vars: weave.legacy.weave.ops.project( vars["entity_name"], vars["project_name"] ).artifact(vars["artifact_name"]), ), diff --git a/weave/legacy/ecosystem/wandb/weave_plotly.py b/weave/legacy/weave/ecosystem/wandb/weave_plotly.py similarity index 97% rename from weave/legacy/ecosystem/wandb/weave_plotly.py rename to weave/legacy/weave/ecosystem/wandb/weave_plotly.py index ab098cf4893..31f71fb54d3 100644 --- a/weave/legacy/ecosystem/wandb/weave_plotly.py +++ b/weave/legacy/weave/ecosystem/wandb/weave_plotly.py @@ -9,8 +9,8 @@ from plotly import graph_objs as go import weave -from weave import weave_internal -from weave.legacy import infer_types +from weave.legacy.weave import weave_internal +from weave.legacy.weave import infer_types class PlotlyType(weave.types.Type): @@ -178,7 +178,7 @@ def bin_center(bin): @weave.op() def plotly_scatter(data: list[ScatterData]) -> plotly.graph_objs.Figure: - from weave.legacy import storage + from weave.legacy.weave import storage data = storage.to_weavejs(data) color = None diff --git a/weave/legacy/ecosystem/shawn/__init__.py b/weave/legacy/weave/ecosystem/xgboost/__init__.py similarity index 51% rename from weave/legacy/ecosystem/shawn/__init__.py rename to weave/legacy/weave/ecosystem/xgboost/__init__.py index 6c2526057a3..af07721cc2a 100644 --- a/weave/legacy/ecosystem/shawn/__init__.py +++ b/weave/legacy/weave/ecosystem/xgboost/__init__.py @@ -1,7 +1,7 @@ -from weave.legacy import context_state as _context +from weave.legacy.weave import context_state as _context _loading_builtins_token = _context.set_loading_built_ins() -from weave.legacy.ecosystem.shawn import petdataset, scratch +from weave.legacy.weave.ecosystem.xgboost.model import * _context.clear_loading_built_ins(_loading_builtins_token) diff --git a/weave/legacy/ecosystem/xgboost/model.py b/weave/legacy/weave/ecosystem/xgboost/model.py similarity index 100% rename from weave/legacy/ecosystem/xgboost/model.py rename to weave/legacy/weave/ecosystem/xgboost/model.py diff --git a/weave/legacy/engine_trace.py b/weave/legacy/weave/engine_trace.py similarity index 98% rename from weave/legacy/engine_trace.py rename to weave/legacy/weave/engine_trace.py index 20ce84169e7..b4b6fca18ba 100644 --- a/weave/legacy/engine_trace.py +++ b/weave/legacy/weave/engine_trace.py @@ -18,7 +18,7 @@ import time import typing -from weave.legacy import environment, logs, stream_data_interfaces +from weave.legacy.weave import environment, logs, stream_data_interfaces # Thanks co-pilot! @@ -91,7 +91,7 @@ def current_root_span(self): # type: ignore def weave_trace_stream(): # type: ignore global _weave_trace_stream if _weave_trace_stream is None: - from weave.legacy.wandb_interface.wandb_stream_table import StreamTable + from weave.legacy.weave.wandb_interface.wandb_stream_table import StreamTable _weave_trace_stream = StreamTable(os.getenv("WEAVE_TRACE_STREAM")) return _weave_trace_stream diff --git a/weave/legacy/environment.py b/weave/legacy/weave/environment.py similarity index 98% rename from weave/legacy/environment.py rename to weave/legacy/weave/environment.py index a2bc2d81852..f9418520a1e 100644 --- a/weave/legacy/environment.py +++ b/weave/legacy/weave/environment.py @@ -10,11 +10,11 @@ import typing from distutils.util import strtobool from urllib.parse import urlparse -from weave.legacy import errors -from weave.legacy import util +from weave.legacy.weave import errors +from weave.legacy.weave import util if typing.TYPE_CHECKING: - from weave.legacy import logs + from weave.legacy.weave import logs WANDB_ERROR_REPORTING = "WANDB_ERROR_REPORTING" WEAVE_USAGE_ANALYTICS = "WEAVE_USAGE_ANALYTICS" @@ -105,7 +105,7 @@ def is_public() -> bool: def weave_log_format(default: "logs.LogFormat") -> "logs.LogFormat": - from weave.legacy.logs import LogFormat + from weave.legacy.weave.logs import LogFormat return LogFormat(os.getenv("WEAVE_LOG_FORMAT", default)) diff --git a/weave/legacy/errors.py b/weave/legacy/weave/errors.py similarity index 100% rename from weave/legacy/errors.py rename to weave/legacy/weave/errors.py diff --git a/weave/legacy/execute.py b/weave/legacy/weave/execute.py similarity index 99% rename from weave/legacy/execute.py rename to weave/legacy/weave/execute.py index b4cc7a81570..42ff32ba230 100644 --- a/weave/legacy/execute.py +++ b/weave/legacy/weave/execute.py @@ -9,7 +9,7 @@ import typing from collections.abc import Mapping -from weave.legacy import weave_types as types +from weave.legacy.weave import weave_types as types # Configuration # Libraries @@ -17,7 +17,7 @@ # Ops # Trace / cache # Language Features -from weave.legacy import ( +from weave.legacy.weave import ( errors, engine_trace, environment, @@ -42,7 +42,7 @@ value_or_error, wandb_api, ) -from weave.legacy.language_features.tagging import ( +from weave.legacy.weave.language_features.tagging import ( opdef_util, process_opdef_resolve_fn, tag_store, diff --git a/weave/legacy/execute_fast.py b/weave/legacy/weave/execute_fast.py similarity index 97% rename from weave/legacy/execute_fast.py rename to weave/legacy/weave/execute_fast.py index b7331ded998..b1bb0f2b87d 100644 --- a/weave/legacy/execute_fast.py +++ b/weave/legacy/weave/execute_fast.py @@ -1,10 +1,8 @@ import logging -from weave import ( +from weave.legacy.weave import weave_types as types +from weave.legacy.weave import ( weave_internal, -) -from weave.legacy import weave_types as types -from weave.legacy import ( errors, engine_trace, ref_base, @@ -16,7 +14,7 @@ language_nullability, op_policy, ) -from weave.legacy.language_features.tagging import tag_store +from weave.legacy.weave.language_features.tagging import tag_store # from . import object_context diff --git a/weave/legacy/file_base.py b/weave/legacy/weave/file_base.py similarity index 98% rename from weave/legacy/file_base.py rename to weave/legacy/weave/file_base.py index ff03c072471..835d7e1a2f6 100644 --- a/weave/legacy/file_base.py +++ b/weave/legacy/weave/file_base.py @@ -2,7 +2,7 @@ import dataclasses import typing -from weave.legacy import weave_types as types +from weave.legacy.weave import weave_types as types TRACE_EXT = "trace.json" diff --git a/weave/legacy/file_local.py b/weave/legacy/weave/file_local.py similarity index 98% rename from weave/legacy/file_local.py rename to weave/legacy/weave/file_local.py index e20a0a2248b..b68d0cd5c83 100644 --- a/weave/legacy/file_local.py +++ b/weave/legacy/weave/file_local.py @@ -2,7 +2,7 @@ import os import typing -from weave.legacy import weave_types as types +from weave.legacy.weave import weave_types as types from . import file_base, file_util diff --git a/weave/legacy/file_util.py b/weave/legacy/weave/file_util.py similarity index 88% rename from weave/legacy/file_util.py rename to weave/legacy/weave/file_util.py index 31da6fc6647..3946e1e5d57 100644 --- a/weave/legacy/file_util.py +++ b/weave/legacy/weave/file_util.py @@ -2,8 +2,8 @@ import pathlib import typing -from weave.legacy import filesystem -from weave.legacy import cache, environment, path_util +from weave.legacy.weave import filesystem +from weave.legacy.weave import cache, environment, path_util def get_allowed_dir() -> pathlib.Path: diff --git a/weave/legacy/filesystem.py b/weave/legacy/weave/filesystem.py similarity index 98% rename from weave/legacy/filesystem.py rename to weave/legacy/weave/filesystem.py index 559ecaa1bd8..c812090c67a 100644 --- a/weave/legacy/filesystem.py +++ b/weave/legacy/weave/filesystem.py @@ -20,7 +20,7 @@ from aiofiles.threadpool import binary as aiofiles_binary from aiofiles.threadpool import text as aiofiles_text -from weave.legacy import cache, engine_trace, environment, errors, util +from weave.legacy.weave import cache, engine_trace, environment, errors, util tracer = engine_trace.tracer() # type: ignore async_utime = aiofiles_os.wrap(os.utime) # type: ignore diff --git a/weave/legacy/forward_graph.py b/weave/legacy/weave/forward_graph.py similarity index 98% rename from weave/legacy/forward_graph.py rename to weave/legacy/weave/forward_graph.py index 7f3801e3024..e22ea9dfdfc 100644 --- a/weave/legacy/forward_graph.py +++ b/weave/legacy/weave/forward_graph.py @@ -3,9 +3,9 @@ import contextvars import typing -from weave.legacy import graph +from weave.legacy.weave import graph -from weave.legacy import errors +from weave.legacy.weave import errors ExecutableNode = typing.Union[graph.OutputNode, graph.ConstNode] diff --git a/weave/legacy/gql_json_cache.py b/weave/legacy/weave/gql_json_cache.py similarity index 100% rename from weave/legacy/gql_json_cache.py rename to weave/legacy/weave/gql_json_cache.py diff --git a/weave/legacy/gql_op_plugin.py b/weave/legacy/weave/gql_op_plugin.py similarity index 95% rename from weave/legacy/gql_op_plugin.py rename to weave/legacy/weave/gql_op_plugin.py index 090a1536c40..1b2af19c287 100644 --- a/weave/legacy/gql_op_plugin.py +++ b/weave/legacy/weave/gql_op_plugin.py @@ -1,8 +1,8 @@ import typing from dataclasses import dataclass -from weave.legacy import weave_types as types -from weave.legacy import input_provider, op_args, op_def +from weave.legacy.weave import weave_types as types +from weave.legacy.weave import input_provider, op_args, op_def # A GQLOutputTypeFn is a function that is called during the refinement phase of the compile pass # to propagate the GQL keys of a node's input types to its output type. diff --git a/weave/legacy/gql_schema.py b/weave/legacy/weave/gql_schema.py similarity index 87% rename from weave/legacy/gql_schema.py rename to weave/legacy/weave/gql_schema.py index 6ee16b3cd7b..eb3181d3be1 100644 --- a/weave/legacy/gql_schema.py +++ b/weave/legacy/weave/gql_schema.py @@ -2,8 +2,8 @@ import graphql -from weave.legacy import environment -from weave.legacy import wandb_client_api +from weave.legacy.weave import environment +from weave.legacy.weave import wandb_client_api _GQL_SCHEMA_CACHE: dict[typing.Optional[str], graphql.GraphQLSchema] = {} diff --git a/weave/legacy/gql_to_weave.py b/weave/legacy/weave/gql_to_weave.py similarity index 97% rename from weave/legacy/gql_to_weave.py rename to weave/legacy/weave/gql_to_weave.py index 117e3511dea..0dae36abee8 100644 --- a/weave/legacy/gql_to_weave.py +++ b/weave/legacy/weave/gql_to_weave.py @@ -17,9 +17,9 @@ parse, ) -from weave.legacy import errors -from weave.legacy import weave_types as types -from weave.legacy import gql_schema +from weave.legacy.weave import errors +from weave.legacy.weave import weave_types as types +from weave.legacy.weave import gql_schema def get_outermost_alias(query_str: str) -> str: diff --git a/weave/legacy/graph.py b/weave/legacy/weave/graph.py similarity index 98% rename from weave/legacy/graph.py rename to weave/legacy/weave/graph.py index 9517b687b34..71474069a46 100644 --- a/weave/legacy/graph.py +++ b/weave/legacy/weave/graph.py @@ -2,11 +2,11 @@ import json import typing -from weave.legacy import weave_types -from weave.legacy import uris, errors, storage +from weave.legacy.weave import weave_types +from weave.legacy.weave import uris, errors, storage if typing.TYPE_CHECKING: - from weave.legacy import weave_inspector + from weave.legacy.weave import weave_inspector T = typing.TypeVar("T") @@ -39,7 +39,7 @@ def to_json(self) -> dict: def _inspect(self) -> "weave_inspector.NodeInspector": """Only intended to be used by developers to help debug the graph.""" # Circular import, so we do it here. - from weave.legacy import weave_inspector + from weave.legacy.weave import weave_inspector return weave_inspector.NodeInspector(self) @@ -187,7 +187,7 @@ def __init__(self, type: weave_types.Type, val: typing.Any) -> None: @classmethod def from_json(cls, obj: dict) -> "ConstNode": - from weave.legacy import dispatch + from weave.legacy.weave import dispatch val = obj["val"] if isinstance(val, dict) and "nodeType" in val: @@ -239,7 +239,7 @@ def op_full_name(op: Op) -> str: def node_expr_str(node: Node) -> str: - from weave.legacy import partial_object + from weave.legacy.weave import partial_object if isinstance(node, OutputNode): param_names = list(node.from_op.inputs.keys()) diff --git a/weave/legacy/graph_debug.py b/weave/legacy/weave/graph_debug.py similarity index 98% rename from weave/legacy/graph_debug.py rename to weave/legacy/weave/graph_debug.py index d82a7353661..539ba38b8af 100644 --- a/weave/legacy/graph_debug.py +++ b/weave/legacy/weave/graph_debug.py @@ -2,8 +2,8 @@ import textwrap import typing -from weave.legacy import weave_types as types -from weave.legacy import forward_graph, graph +from weave.legacy.weave import weave_types as types +from weave.legacy.weave import forward_graph, graph class _CombinedConstVal: @@ -123,7 +123,7 @@ def node_expr_str_full(node: graph.Node) -> str: This function is a copy/modification of of node_expr_str. """ - from weave.legacy import partial_object + from weave.legacy.weave import partial_object if isinstance(node, graph.OutputNode): if node.from_op.name == "dict": diff --git a/weave/legacy/graph_mapper.py b/weave/legacy/weave/graph_mapper.py similarity index 75% rename from weave/legacy/graph_mapper.py rename to weave/legacy/weave/graph_mapper.py index 1cf5a6c9e13..fab4bfc6cd2 100644 --- a/weave/legacy/graph_mapper.py +++ b/weave/legacy/weave/graph_mapper.py @@ -1,7 +1,7 @@ -from weave.legacy import ref_base -from weave.legacy import weave_types as types -from weave.legacy import graph, mappers, node_ref -from weave.legacy import mappers_python_def as mappers_python +from weave.legacy.weave import ref_base +from weave.legacy.weave import weave_types as types +from weave.legacy.weave import graph, mappers, node_ref +from weave.legacy.weave import mappers_python_def as mappers_python class FunctionToPyFunction(mappers.Mapper): diff --git a/weave/legacy/infer_types.py b/weave/legacy/weave/infer_types.py similarity index 97% rename from weave/legacy/infer_types.py rename to weave/legacy/weave/infer_types.py index 9eedc3a4c03..018b485d00f 100644 --- a/weave/legacy/infer_types.py +++ b/weave/legacy/weave/infer_types.py @@ -6,9 +6,9 @@ import typing_extensions -from weave.legacy import errors, graph +from weave.legacy.weave import errors, graph -from weave.legacy import weave_types +from weave.legacy.weave import weave_types class TypedDictLike: diff --git a/weave/legacy/input_provider.py b/weave/legacy/weave/input_provider.py similarity index 100% rename from weave/legacy/input_provider.py rename to weave/legacy/weave/input_provider.py diff --git a/weave/legacy/io_service.py b/weave/legacy/weave/io_service.py similarity index 99% rename from weave/legacy/io_service.py rename to weave/legacy/weave/io_service.py index 9091573a914..8e19392040b 100644 --- a/weave/legacy/io_service.py +++ b/weave/legacy/weave/io_service.py @@ -22,7 +22,7 @@ import aioprocessing -from weave.legacy import ( +from weave.legacy.weave import ( weave_http, filesystem, errors, diff --git a/weave/legacy/language_autocall.py b/weave/legacy/weave/language_autocall.py similarity index 93% rename from weave/legacy/language_autocall.py rename to weave/legacy/weave/language_autocall.py index 73822279ed7..97f87e7f01b 100644 --- a/weave/legacy/language_autocall.py +++ b/weave/legacy/weave/language_autocall.py @@ -15,8 +15,8 @@ import typing -from weave.legacy import weave_types as types -from weave.legacy import op_args +from weave.legacy.weave import weave_types as types +from weave.legacy.weave import op_args def update_input_types( diff --git a/weave/legacy/weave/language_features/__init__.py b/weave/legacy/weave/language_features/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/weave/legacy/weave/language_features/tagging/__init__.py b/weave/legacy/weave/language_features/tagging/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/weave/legacy/language_features/tagging/is_tag_getter.py b/weave/legacy/weave/language_features/tagging/is_tag_getter.py similarity index 84% rename from weave/legacy/language_features/tagging/is_tag_getter.py rename to weave/legacy/weave/language_features/tagging/is_tag_getter.py index d4d6b83c401..ccc4969f256 100644 --- a/weave/legacy/language_features/tagging/is_tag_getter.py +++ b/weave/legacy/weave/language_features/tagging/is_tag_getter.py @@ -1,11 +1,11 @@ import typing -from weave.legacy import weave_types as types -from weave.legacy import op_args -from weave.legacy.language_features.tagging import tagged_value_type +from weave.legacy.weave import weave_types as types +from weave.legacy.weave import op_args +from weave.legacy.weave.language_features.tagging import tagged_value_type if typing.TYPE_CHECKING: - from weave.legacy import op_def as OpDef + from weave.legacy.weave import op_def as OpDef # This is a heuristic that is used to determine if an op is a tag getter. diff --git a/weave/legacy/language_features/tagging/make_tag_getter_op.py b/weave/legacy/weave/language_features/tagging/make_tag_getter_op.py similarity index 87% rename from weave/legacy/language_features/tagging/make_tag_getter_op.py rename to weave/legacy/weave/language_features/tagging/make_tag_getter_op.py index 9cb06bd9f2f..cd807c2c2ac 100644 --- a/weave/legacy/language_features/tagging/make_tag_getter_op.py +++ b/weave/legacy/weave/language_features/tagging/make_tag_getter_op.py @@ -1,12 +1,12 @@ import typing -from weave.legacy import weave_types as types -from weave.legacy import context_state as _context_state -from weave.legacy import decorator_op -from weave.legacy.language_features.tagging import tag_store, tagged_value_type +from weave.legacy.weave import weave_types as types +from weave.legacy.weave import context_state as _context_state +from weave.legacy.weave import decorator_op +from weave.legacy.weave.language_features.tagging import tag_store, tagged_value_type if typing.TYPE_CHECKING: - from weave.legacy import op_def as OpDef + from weave.legacy.weave import op_def as OpDef def make_tag_getter_op( @@ -26,7 +26,7 @@ def make_tag_getter_op( Returns: The op. """ - from weave.legacy.arrow.list_ import ArrowWeaveList, ArrowWeaveListType + from weave.legacy.weave.arrow.list_ import ArrowWeaveList, ArrowWeaveListType _loading_builtins_token = _context_state.set_loading_built_ins() diff --git a/weave/legacy/language_features/tagging/opdef_util.py b/weave/legacy/weave/language_features/tagging/opdef_util.py similarity index 91% rename from weave/legacy/language_features/tagging/opdef_util.py rename to weave/legacy/weave/language_features/tagging/opdef_util.py index f52a748414b..2e5dee35dc4 100644 --- a/weave/legacy/language_features/tagging/opdef_util.py +++ b/weave/legacy/weave/language_features/tagging/opdef_util.py @@ -1,10 +1,10 @@ import typing -from weave.legacy import weave_types as types -from weave.legacy.language_features.tagging import tagged_value_type +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.language_features.tagging import tagged_value_type if typing.TYPE_CHECKING: - from weave.legacy import op_def as OpDef + from weave.legacy.weave import op_def as OpDef # Determines if the op_def should ftag the output with the input. Currently, this is true diff --git a/weave/legacy/language_features/tagging/process_opdef_output_type.py b/weave/legacy/weave/language_features/tagging/process_opdef_output_type.py similarity index 85% rename from weave/legacy/language_features/tagging/process_opdef_output_type.py rename to weave/legacy/weave/language_features/tagging/process_opdef_output_type.py index b9e1a65b4cc..cc9c40913a6 100644 --- a/weave/legacy/language_features/tagging/process_opdef_output_type.py +++ b/weave/legacy/weave/language_features/tagging/process_opdef_output_type.py @@ -5,21 +5,21 @@ import typing -from weave.legacy import registry_mem -from weave.legacy import weave_types as types -from weave.legacy import graph -from weave.legacy.language_features.tagging.opdef_util import ( +from weave.legacy.weave import registry_mem +from weave.legacy.weave import weave_types as types +from weave.legacy.weave import graph +from weave.legacy.weave.language_features.tagging.opdef_util import ( should_flow_tags, should_tag_op_def_outputs, ) -from weave.legacy.language_features.tagging.tagging_op_logic import ( +from weave.legacy.weave.language_features.tagging.tagging_op_logic import ( op_get_tag_type_resolver, op_make_type_key_tag_resolver, op_make_type_tagged_resolver, ) if typing.TYPE_CHECKING: - from weave.legacy import op_def as OpDef + from weave.legacy.weave import op_def as OpDef # The following 3 functions are used to get the ops without introducing circular references diff --git a/weave/legacy/language_features/tagging/process_opdef_resolve_fn.py b/weave/legacy/weave/language_features/tagging/process_opdef_resolve_fn.py similarity index 91% rename from weave/legacy/language_features/tagging/process_opdef_resolve_fn.py rename to weave/legacy/weave/language_features/tagging/process_opdef_resolve_fn.py index 720c8db3931..86e8275df33 100644 --- a/weave/legacy/language_features/tagging/process_opdef_resolve_fn.py +++ b/weave/legacy/weave/language_features/tagging/process_opdef_resolve_fn.py @@ -11,20 +11,20 @@ import typing_extensions from pyarrow import compute as pc -from weave.legacy import weave_types as types -from weave.legacy import box -from weave.legacy.arrow.arrow_tags import awl_add_arrow_tags -from weave.legacy.arrow.list_ import ArrowWeaveList -from weave.legacy.language_features.tagging import tag_store -from weave.legacy.language_features.tagging.opdef_util import ( +from weave.legacy.weave import weave_types as types +from weave.legacy.weave import box +from weave.legacy.weave.arrow.arrow_tags import awl_add_arrow_tags +from weave.legacy.weave.arrow.list_ import ArrowWeaveList +from weave.legacy.weave.language_features.tagging import tag_store +from weave.legacy.weave.language_features.tagging.opdef_util import ( get_first_arg, should_flow_tags, should_tag_op_def_outputs, ) -from weave.legacy.language_features.tagging.tagged_value_type import TaggedValueType +from weave.legacy.weave.language_features.tagging.tagged_value_type import TaggedValueType if typing.TYPE_CHECKING: - from weave.legacy import op_def as OpDef + from weave.legacy.weave import op_def as OpDef def _is_tagged_value(val: types.Type) -> typing_extensions.TypeGuard[TaggedValueType]: @@ -38,7 +38,7 @@ def _is_optional_tagged_value( def _strip_tags(val: typing.Any) -> typing.Any: - from weave.legacy.ops_arrow import ArrowWeaveList + from weave.legacy.weave.ops_arrow import ArrowWeaveList if isinstance(val, ArrowWeaveList): if _is_tagged_value(val.object_type): diff --git a/weave/legacy/language_features/tagging/tag_store.py b/weave/legacy/weave/language_features/tagging/tag_store.py similarity index 98% rename from weave/legacy/language_features/tagging/tag_store.py rename to weave/legacy/weave/language_features/tagging/tag_store.py index f6fea94eb79..577a446cb8c 100644 --- a/weave/legacy/language_features/tagging/tag_store.py +++ b/weave/legacy/weave/language_features/tagging/tag_store.py @@ -22,9 +22,9 @@ from collections import defaultdict from contextlib import contextmanager -from weave.legacy import errors -from weave.legacy import weave_types as types -from weave.legacy import engine_trace, box +from weave.legacy.weave import errors +from weave.legacy.weave import weave_types as types +from weave.legacy.weave import engine_trace, box statsd = engine_trace.statsd() # type: ignore diff --git a/weave/legacy/language_features/tagging/tagged_value_type.py b/weave/legacy/weave/language_features/tagging/tagged_value_type.py similarity index 98% rename from weave/legacy/language_features/tagging/tagged_value_type.py rename to weave/legacy/weave/language_features/tagging/tagged_value_type.py index 9ae900a2835..3a95169fba0 100644 --- a/weave/legacy/language_features/tagging/tagged_value_type.py +++ b/weave/legacy/weave/language_features/tagging/tagged_value_type.py @@ -30,13 +30,13 @@ import json import typing -from weave.legacy import errors -from weave.legacy import weave_types as types -from weave.legacy import box, mappers, mappers_python -from weave.legacy.language_features.tagging import tag_store +from weave.legacy.weave import errors +from weave.legacy.weave import weave_types as types +from weave.legacy.weave import box, mappers, mappers_python +from weave.legacy.weave.language_features.tagging import tag_store if typing.TYPE_CHECKING: - from weave.legacy import artifact_base, artifact_fs + from weave.legacy.weave import artifact_base, artifact_fs def flatten_tag_type_to_typed_dict(tag_type: types.Type) -> types.TypedDict: diff --git a/weave/legacy/language_features/tagging/tagged_value_type_helpers.py b/weave/legacy/weave/language_features/tagging/tagged_value_type_helpers.py similarity index 94% rename from weave/legacy/language_features/tagging/tagged_value_type_helpers.py rename to weave/legacy/weave/language_features/tagging/tagged_value_type_helpers.py index a5b1a83f446..2e3ab0b656c 100644 --- a/weave/legacy/language_features/tagging/tagged_value_type_helpers.py +++ b/weave/legacy/weave/language_features/tagging/tagged_value_type_helpers.py @@ -1,7 +1,7 @@ import typing -from weave.legacy import weave_types as types -from weave.legacy.language_features.tagging import tagged_value_type +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.language_features.tagging import tagged_value_type def push_down_tags_from_container_type_to_element_type( diff --git a/weave/legacy/language_features/tagging/tagging_op_logic.py b/weave/legacy/weave/language_features/tagging/tagging_op_logic.py similarity index 86% rename from weave/legacy/language_features/tagging/tagging_op_logic.py rename to weave/legacy/weave/language_features/tagging/tagging_op_logic.py index 2c2bcc04138..1d88dde0491 100644 --- a/weave/legacy/language_features/tagging/tagging_op_logic.py +++ b/weave/legacy/weave/language_features/tagging/tagging_op_logic.py @@ -1,5 +1,5 @@ -from weave.legacy import weave_types as types -from weave.legacy.language_features.tagging.tagged_value_type import TaggedValueType +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.language_features.tagging.tagged_value_type import TaggedValueType def op_get_tag_type_resolver(obj_type: types.Type) -> types.Type: diff --git a/weave/legacy/language_features/tagging/tagging_ops.py b/weave/legacy/weave/language_features/tagging/tagging_ops.py similarity index 84% rename from weave/legacy/language_features/tagging/tagging_ops.py rename to weave/legacy/weave/language_features/tagging/tagging_ops.py index abb6dfeb9aa..36e7e553d8b 100644 --- a/weave/legacy/language_features/tagging/tagging_ops.py +++ b/weave/legacy/weave/language_features/tagging/tagging_ops.py @@ -1,6 +1,6 @@ -from weave.legacy import weave_types as types -from weave.legacy import decorator_op -from weave.legacy.language_features.tagging import tagging_op_logic +from weave.legacy.weave import weave_types as types +from weave.legacy.weave import decorator_op +from weave.legacy.weave.language_features.tagging import tagging_op_logic @decorator_op.op( diff --git a/weave/legacy/language_nullability.py b/weave/legacy/weave/language_nullability.py similarity index 83% rename from weave/legacy/language_nullability.py rename to weave/legacy/weave/language_nullability.py index 2541721b19a..c5ef29999cd 100644 --- a/weave/legacy/language_nullability.py +++ b/weave/legacy/weave/language_nullability.py @@ -1,11 +1,11 @@ import typing -from weave.legacy import weave_types as types -from weave.legacy import box -from weave.legacy.language_features.tagging import tagged_value_type +from weave.legacy.weave import weave_types as types +from weave.legacy.weave import box +from weave.legacy.weave.language_features.tagging import tagged_value_type if typing.TYPE_CHECKING: - from weave.legacy import op_def as OpDef + from weave.legacy.weave import op_def as OpDef def should_force_none_result( diff --git a/weave/legacy/logs.py b/weave/legacy/weave/logs.py similarity index 99% rename from weave/legacy/logs.py rename to weave/legacy/weave/logs.py index 30c3127834b..a28d7c2bbf3 100644 --- a/weave/legacy/logs.py +++ b/weave/legacy/weave/logs.py @@ -12,7 +12,7 @@ from pythonjsonlogger import jsonlogger -from weave.legacy import environment +from weave.legacy.weave import environment try: from flask.logging import wsgi_errors_stream diff --git a/weave/legacy/make_type.py b/weave/legacy/weave/make_type.py similarity index 81% rename from weave/legacy/make_type.py rename to weave/legacy/weave/make_type.py index 765bc06636d..69a8eb786a7 100644 --- a/weave/legacy/make_type.py +++ b/weave/legacy/weave/make_type.py @@ -1,5 +1,5 @@ -from .. import weave_internal -from weave.legacy import weave_types as types +from weave.legacy.weave import weave_internal +from weave.legacy.weave import weave_types as types # TODO: Consider if this should accept a *args, **kwargs? diff --git a/weave/legacy/mappers.py b/weave/legacy/weave/mappers.py similarity index 81% rename from weave/legacy/mappers.py rename to weave/legacy/weave/mappers.py index b7a25b0e605..28e4d4a785c 100644 --- a/weave/legacy/mappers.py +++ b/weave/legacy/weave/mappers.py @@ -1,8 +1,8 @@ import typing if typing.TYPE_CHECKING: - from weave.legacy import weave_types as types - from weave.legacy import artifact_local + from weave.legacy.weave import weave_types as types + from weave.legacy.weave import artifact_local class Mapper: diff --git a/weave/legacy/mappers_arrow.py b/weave/legacy/weave/mappers_arrow.py similarity index 98% rename from weave/legacy/mappers_arrow.py rename to weave/legacy/weave/mappers_arrow.py index f74e1ca2ba5..35d5cca157d 100644 --- a/weave/legacy/mappers_arrow.py +++ b/weave/legacy/weave/mappers_arrow.py @@ -5,9 +5,9 @@ import pyarrow as pa -from weave.legacy import errors -from weave.legacy import weave_types as types -from weave.legacy import ( +from weave.legacy.weave import errors +from weave.legacy.weave import weave_types as types +from weave.legacy.weave import ( arrow_util, artifact_base, box, @@ -17,9 +17,9 @@ partial_object, ref_base, ) -from weave.legacy import mappers_python_def as mappers_python -from weave.legacy.arrow import arrow -from weave.legacy.language_features.tagging import tagged_value_type +from weave.legacy.weave import mappers_python_def as mappers_python +from weave.legacy.weave.arrow import arrow +from weave.legacy.weave.language_features.tagging import tagged_value_type _in_tagging_context = contextvars.ContextVar("in_tagging_context", default=False) diff --git a/weave/legacy/mappers_gql.py b/weave/legacy/weave/mappers_gql.py similarity index 93% rename from weave/legacy/mappers_gql.py rename to weave/legacy/weave/mappers_gql.py index 1f7da87687e..4abfbcc62b5 100644 --- a/weave/legacy/mappers_gql.py +++ b/weave/legacy/weave/mappers_gql.py @@ -1,10 +1,10 @@ import json -from weave.legacy import errors -from weave.legacy import weave_types as types -from weave.legacy import gql_json_cache, mappers -from weave.legacy.mappers_weave import UnionMapper -from weave.legacy.partial_object import PartialObjectType +from weave.legacy.weave import errors +from weave.legacy.weave import weave_types as types +from weave.legacy.weave import gql_json_cache, mappers +from weave.legacy.weave.mappers_weave import UnionMapper +from weave.legacy.weave.partial_object import PartialObjectType from .mappers_python_def import ( BoolToPyBool, diff --git a/weave/legacy/mappers_publisher.py b/weave/legacy/weave/mappers_publisher.py similarity index 92% rename from weave/legacy/mappers_publisher.py rename to weave/legacy/weave/mappers_publisher.py index 667d47a5414..2a53e30e3fa 100644 --- a/weave/legacy/mappers_publisher.py +++ b/weave/legacy/weave/mappers_publisher.py @@ -2,13 +2,13 @@ import dataclasses import typing -from weave.legacy import storage -from weave import weave_internal -from weave.legacy import weave_types as types -from weave.legacy.node_ref import ref_to_node +from weave.legacy.weave import storage +from weave.legacy.weave import weave_internal +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.node_ref import ref_to_node -# from weave.legacy.ops_primitives import weave_api -from weave.legacy import ( +# from weave.legacy.weave.ops_primitives import weave_api +from weave.legacy.weave import ( artifact_local, box, context, @@ -19,9 +19,9 @@ ref_base, errors, ) -from weave.legacy.artifact_wandb import likely_commit_hash -from weave.legacy.language_features.tagging import tag_store, tagged_value_type -from weave.legacy.uris import WeaveURI +from weave.legacy.weave.artifact_wandb import likely_commit_hash +from weave.legacy.weave.language_features.tagging import tag_store, tagged_value_type +from weave.legacy.weave.uris import WeaveURI class RefToPyRef(mappers.Mapper): @@ -128,7 +128,7 @@ def map_to_python_remote_(type, mapper, artifact, path=[], mapper_options=None): def _node_publish_mapper(node: graph.Node) -> typing.Optional[graph.Node]: - from weave.legacy.ops_primitives import weave_api + from weave.legacy.weave.ops_primitives import weave_api if _node_is_op_get(node): node = typing.cast(graph.OutputNode, node) diff --git a/weave/legacy/mappers_python.py b/weave/legacy/weave/mappers_python.py similarity index 85% rename from weave/legacy/mappers_python.py rename to weave/legacy/weave/mappers_python.py index ad3f240eb35..30b41aee953 100644 --- a/weave/legacy/mappers_python.py +++ b/weave/legacy/weave/mappers_python.py @@ -3,8 +3,8 @@ import typing if typing.TYPE_CHECKING: - from weave.legacy import weave_types - from weave.legacy import artifact_base, mappers + from weave.legacy.weave import weave_types + from weave.legacy.weave import artifact_base, mappers def map_to_python( # type: ignore[empty-body] diff --git a/weave/legacy/mappers_python_def.py b/weave/legacy/weave/mappers_python_def.py similarity index 97% rename from weave/legacy/mappers_python_def.py rename to weave/legacy/weave/mappers_python_def.py index 5a1ee099220..7951b462060 100644 --- a/weave/legacy/mappers_python_def.py +++ b/weave/legacy/weave/mappers_python_def.py @@ -4,9 +4,9 @@ import math import typing -from weave.legacy import storage -from weave.legacy import weave_types as types -from weave.legacy import ( +from weave.legacy.weave import storage +from weave.legacy.weave import weave_types as types +from weave.legacy.weave import ( artifact_fs, box, mappers, @@ -16,10 +16,10 @@ val_const, errors, ) -from weave.legacy import timestamp as weave_timestamp -from weave.legacy.language_features.tagging import tagged_value_type -from weave.legacy.partial_object import PartialObject, PartialObjectType -from weave.client_context import weave_client as weave_client_context +from weave.legacy.weave import timestamp as weave_timestamp +from weave.legacy.weave.language_features.tagging import tagged_value_type +from weave.legacy.weave.partial_object import PartialObject, PartialObjectType +from weave.trace.client_context import weave_client as weave_client_context class TypedDictToPyDict(mappers_weave.TypedDictMapper): @@ -61,7 +61,7 @@ def apply(self, obj): class ObjectDictToObject(mappers_weave.ObjectMapper): def apply(self, obj): - from weave.legacy.op_def_type import OpDefType + from weave.legacy.weave.op_def_type import OpDefType # Only add keys that are accepted by the constructor. # This is used for Panels where we have an Class-level id attribute @@ -333,7 +333,7 @@ def __init__( self._use_stable_refs = use_stable_refs def apply(self, obj): - from weave.legacy import op_def + from weave.legacy.weave import op_def try: return self.type.instance_to_dict(obj) diff --git a/weave/legacy/mappers_weave.py b/weave/legacy/weave/mappers_weave.py similarity index 95% rename from weave/legacy/mappers_weave.py rename to weave/legacy/weave/mappers_weave.py index 133e1456cce..e33916f7670 100644 --- a/weave/legacy/mappers_weave.py +++ b/weave/legacy/weave/mappers_weave.py @@ -1,8 +1,8 @@ import typing -from weave.legacy import weave_types as types -from weave.legacy import mappers -from weave.legacy.partial_object import PartialObjectType +from weave.legacy.weave import weave_types as types +from weave.legacy.weave import mappers +from weave.legacy.weave.partial_object import PartialObjectType class TypedDictMapper(mappers.Mapper): diff --git a/weave/legacy/memo.py b/weave/legacy/weave/memo.py similarity index 97% rename from weave/legacy/memo.py rename to weave/legacy/weave/memo.py index 4928bb9b062..379a76fd035 100644 --- a/weave/legacy/memo.py +++ b/weave/legacy/weave/memo.py @@ -2,7 +2,7 @@ import contextvars import typing -from weave.legacy import engine_trace +from weave.legacy.weave import engine_trace statsd = engine_trace.statsd() # type: ignore diff --git a/weave/legacy/monitoring/__init__.py b/weave/legacy/weave/monitoring/__init__.py similarity index 58% rename from weave/legacy/monitoring/__init__.py rename to weave/legacy/weave/monitoring/__init__.py index 8a0b5fd944e..e40a17557df 100644 --- a/weave/legacy/monitoring/__init__.py +++ b/weave/legacy/weave/monitoring/__init__.py @@ -2,11 +2,11 @@ # TODO: Notebook Walkthrough -from weave.legacy.monitoring.monitor import ( +from weave.legacy.weave.monitoring.monitor import ( default_monitor, deinit_monitor, init_monitor, ) -from weave.legacy.wandb_interface.wandb_stream_table import StreamTable +from weave.legacy.weave.wandb_interface.wandb_stream_table import StreamTable __all__ = ["StreamTable", "monitor", "openai"] diff --git a/weave/legacy/monitoring/langchain.py b/weave/legacy/weave/monitoring/langchain.py similarity index 94% rename from weave/legacy/monitoring/langchain.py rename to weave/legacy/weave/monitoring/langchain.py index 8e3afddc148..84371bbefee 100644 --- a/weave/legacy/monitoring/langchain.py +++ b/weave/legacy/weave/monitoring/langchain.py @@ -11,8 +11,8 @@ from langchain.callbacks.tracers.schemas import Run from wandb.sdk.data_types import trace_tree -from weave.legacy import stream_data_interfaces -from weave.legacy.monitoring import StreamTable +from weave.legacy.weave import stream_data_interfaces +from weave.legacy.weave.monitoring import StreamTable def _hash_id(s: str) -> str: diff --git a/weave/legacy/monitoring/monitor.py b/weave/legacy/weave/monitoring/monitor.py similarity index 98% rename from weave/legacy/monitoring/monitor.py rename to weave/legacy/weave/monitoring/monitor.py index 5a707af576c..440a72d1291 100644 --- a/weave/legacy/monitoring/monitor.py +++ b/weave/legacy/weave/monitoring/monitor.py @@ -11,15 +11,15 @@ import typing import uuid -from weave.legacy import errors -from weave.legacy import ( +from weave.legacy.weave import errors +from weave.legacy.weave import ( graph, run_streamtable_span, stream_data_interfaces ) -from weave.legacy.wandb_interface.wandb_stream_table import StreamTable +from weave.legacy.weave.wandb_interface.wandb_stream_table import StreamTable from weave.trace import call_context, context as trace_context -from weave.client_context import weave_client as weave_client_context +from weave.trace.client_context import weave_client as weave_client_context logger = logging.getLogger(__name__) diff --git a/weave/legacy/monitoring/openai/__init__.py b/weave/legacy/weave/monitoring/openai/__init__.py similarity index 100% rename from weave/legacy/monitoring/openai/__init__.py rename to weave/legacy/weave/monitoring/openai/__init__.py diff --git a/weave/legacy/monitoring/openai/models.py b/weave/legacy/weave/monitoring/openai/models.py similarity index 90% rename from weave/legacy/monitoring/openai/models.py rename to weave/legacy/weave/monitoring/openai/models.py index d9688f8b281..479d497c189 100644 --- a/weave/legacy/monitoring/openai/models.py +++ b/weave/legacy/weave/monitoring/openai/models.py @@ -7,8 +7,8 @@ ) from pydantic import BaseModel, Field -from weave.legacy import weave_types as types -from weave.legacy.monitoring import monitor +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.monitoring import monitor class ModelTokensConfig(BaseModel): diff --git a/weave/legacy/monitoring/openai/openai.py b/weave/legacy/weave/monitoring/openai/openai.py similarity index 97% rename from weave/legacy/monitoring/openai/openai.py rename to weave/legacy/weave/monitoring/openai/openai.py index c002ec85c4c..1e2b99a3951 100644 --- a/weave/legacy/monitoring/openai/openai.py +++ b/weave/legacy/weave/monitoring/openai/openai.py @@ -11,10 +11,10 @@ from openai.types.chat import ChatCompletion, ChatCompletionMessageParam from packaging import version from weave.trace import call_context -from weave.client_context import weave_client as weave_client_context -from weave.legacy.monitoring.monitor import _get_global_monitor -from weave.legacy.monitoring.openai.models import * -from weave.legacy.monitoring.openai.util import * +from weave.trace.client_context import weave_client as weave_client_context +from weave.legacy.weave.monitoring.monitor import _get_global_monitor +from weave.legacy.weave.monitoring.openai.models import * +from weave.legacy.weave.monitoring.openai.util import * from weave.trace.op import Op from weave.trace.op import op as op_deco diff --git a/weave/legacy/monitoring/openai/util.py b/weave/legacy/weave/monitoring/openai/util.py similarity index 98% rename from weave/legacy/monitoring/openai/util.py rename to weave/legacy/weave/monitoring/openai/util.py index e8772fa289d..e20f2fddbb0 100644 --- a/weave/legacy/monitoring/openai/util.py +++ b/weave/legacy/weave/monitoring/openai/util.py @@ -8,7 +8,7 @@ from openai.types.chat.chat_completion_chunk import ChatCompletionChunk, Choice from openai.types.completion_usage import CompletionUsage -from weave.legacy.monitoring.openai.models import * +from weave.legacy.weave.monitoring.openai.models import * T = TypeVar("T") diff --git a/weave/legacy/node_ref.py b/weave/legacy/weave/node_ref.py similarity index 97% rename from weave/legacy/node_ref.py rename to weave/legacy/weave/node_ref.py index 47edbc6804b..2a130af4749 100644 --- a/weave/legacy/node_ref.py +++ b/weave/legacy/weave/node_ref.py @@ -1,7 +1,7 @@ import copy import typing -from weave.legacy import graph +from weave.legacy.weave import graph from . import ref_base @@ -88,7 +88,7 @@ def ref_to_node(ref: ref_base.Ref) -> typing.Optional[graph.Node]: ref = copy.copy(ref) ref.extra = [] - from weave.legacy.ops_primitives import get + from weave.legacy.weave.ops_primitives import get node = get(str(ref)) for str_key in extra: diff --git a/weave/legacy/object_context.py b/weave/legacy/weave/object_context.py similarity index 95% rename from weave/legacy/object_context.py rename to weave/legacy/weave/object_context.py index 3ed119fa154..d062a02652c 100644 --- a/weave/legacy/object_context.py +++ b/weave/legacy/weave/object_context.py @@ -12,9 +12,9 @@ from typing import Any if typing.TYPE_CHECKING: - from weave.legacy import ref_base -from weave.legacy import weave_types as types -from weave.legacy import box, uris + from weave.legacy.weave import ref_base +from weave.legacy.weave import weave_types as types +from weave.legacy.weave import box, uris @dataclasses.dataclass @@ -90,7 +90,7 @@ def add_mutation( self.objects[target_uri].mutations.append(mutation) def finish_mutation(self, target_uri: str) -> None: - from weave.legacy import artifact_fs, artifact_local, artifact_wandb + from weave.legacy.weave import artifact_fs, artifact_local, artifact_wandb target_record = self.objects.get(target_uri) if target_record is None: @@ -115,7 +115,7 @@ def finish_mutation(self, target_uri: str) -> None: if target_branch is None: raise ValueError("No branch to finish mutation on") - from weave.legacy import artifact_wandb + from weave.legacy.weave import artifact_wandb # Hack: if the target branch looks like a commit hash, then we # don't want to use it as a branch - this is the case that we are diff --git a/weave/legacy/object_type_ref_util.py b/weave/legacy/weave/object_type_ref_util.py similarity index 97% rename from weave/legacy/object_type_ref_util.py rename to weave/legacy/weave/object_type_ref_util.py index 53787c4c454..d0bc6a1ee35 100644 --- a/weave/legacy/object_type_ref_util.py +++ b/weave/legacy/weave/object_type_ref_util.py @@ -1,7 +1,7 @@ import typing from weave.trace import ref_util -from weave.legacy import context_state +from weave.legacy.weave import context_state def make_object_getattribute( diff --git a/weave/legacy/op_aliases.py b/weave/legacy/weave/op_aliases.py similarity index 100% rename from weave/legacy/op_aliases.py rename to weave/legacy/weave/op_aliases.py diff --git a/weave/legacy/op_args.py b/weave/legacy/weave/op_args.py similarity index 98% rename from weave/legacy/op_args.py rename to weave/legacy/weave/op_args.py index 15fae9a443b..e0dd2400501 100644 --- a/weave/legacy/op_args.py +++ b/weave/legacy/weave/op_args.py @@ -1,8 +1,8 @@ import typing from dataclasses import dataclass -from weave.legacy import weave_types as types -from weave.legacy import debug_types +from weave.legacy.weave import weave_types as types +from weave.legacy.weave import debug_types BT = typing.TypeVar("BT") diff --git a/weave/legacy/op_def.py b/weave/legacy/weave/op_def.py similarity index 97% rename from weave/legacy/op_def.py rename to weave/legacy/weave/op_def.py index 7581773e09f..f8e397a375e 100644 --- a/weave/legacy/op_def.py +++ b/weave/legacy/weave/op_def.py @@ -6,11 +6,9 @@ import typing from typing import Iterator, Sequence -from weave import ( +from weave.legacy.weave import weave_types as types +from weave.legacy.weave import ( weave_internal, -) -from weave.legacy import weave_types as types -from weave.legacy import ( errors, context_state, engine_trace, @@ -24,18 +22,18 @@ pyfunc_type_util, uris, ) -from weave.legacy.language_features.tagging import ( +from weave.legacy.weave.language_features.tagging import ( opdef_util, process_opdef_output_type, process_opdef_resolve_fn, tagged_value_type, ) -from weave.legacy.run import Run -from weave.legacy.weavejs_fixes import fixup_node +from weave.legacy.weave.run import Run +from weave.legacy.weave.weavejs_fixes import fixup_node if typing.TYPE_CHECKING: from weave.trace import weave_client - from weave.legacy.run_streamtable_span import RunStreamTableSpan + from weave.legacy.weave.run_streamtable_span import RunStreamTableSpan _no_refine: contextvars.ContextVar[bool] = contextvars.ContextVar( @@ -291,7 +289,7 @@ def __call__(_self, *args, **kwargs): # convert arguments to Const nodes. There is no type checking. # May need to fix this, but the patterns in test_mutation2 work # now. - from weave.legacy import object_context + from weave.legacy.weave import object_context with object_context.object_context(): return _self.resolve_fn(*args, **kwargs) @@ -315,7 +313,7 @@ def unrefined_output_type_for_params(self, params: typing.Dict[str, graph.Node]) return self.output_type(new_input_type) def is_gql_root_resolver(self): - from weave.legacy import gql_op_plugin + from weave.legacy.weave import gql_op_plugin return self in gql_op_plugin._ROOT_RESOLVERS @@ -358,14 +356,14 @@ def _replace_var_with_val(n): tracer = engine_trace.tracer() # type: ignore with tracer.trace("refine.%s" % _self.uri): # api's use auto-creates client. TODO: Fix inline import - from weave.legacy import api as api + from weave.legacy.weave import api as api final_output_type = api.use(called_refine_output_type) # type: ignore if final_output_type == None: # This can happen due to nullability. In that case, accept the output type is null final_output_type = types.NoneType() # Have to deref if in case a ref came back... - from weave.legacy import storage + from weave.legacy.weave import storage final_output_type = storage.deref(final_output_type) @@ -376,7 +374,7 @@ def _replace_var_with_val(n): ) else: final_output_type = _self.unrefined_output_type_for_params(bound_params) - from weave.legacy import dispatch + from weave.legacy.weave import dispatch return dispatch.RuntimeOutputNode(final_output_type, _self.uri, bound_params) diff --git a/weave/legacy/op_def_type.py b/weave/legacy/weave/op_def_type.py similarity index 99% rename from weave/legacy/op_def_type.py rename to weave/legacy/weave/op_def_type.py index 8fd598aa0ca..472f1207d9f 100644 --- a/weave/legacy/op_def_type.py +++ b/weave/legacy/weave/op_def_type.py @@ -11,9 +11,9 @@ from _ast import AsyncFunctionDef, ExceptHandler from typing import Any -from weave.legacy import storage -from weave.legacy import weave_types as types -from weave.legacy import ( +from weave.legacy.weave import storage +from weave.legacy.weave import weave_types as types +from weave.legacy.weave import ( artifact_fs, artifact_local, context_state, @@ -446,7 +446,7 @@ def load_instance(cls, artifact, name, extra=None): except FileNotFoundError: pass - from weave.legacy import artifact_wandb + from weave.legacy.weave import artifact_wandb is_wandb_artifact = False if isinstance(artifact, artifact_wandb.WandbArtifact): diff --git a/weave/legacy/op_execute.py b/weave/legacy/weave/op_execute.py similarity index 100% rename from weave/legacy/op_execute.py rename to weave/legacy/weave/op_execute.py diff --git a/weave/legacy/op_policy.py b/weave/legacy/weave/op_policy.py similarity index 100% rename from weave/legacy/op_policy.py rename to weave/legacy/weave/op_policy.py diff --git a/weave/legacy/ops.py b/weave/legacy/weave/ops.py similarity index 100% rename from weave/legacy/ops.py rename to weave/legacy/weave/ops.py diff --git a/weave/legacy/weave/ops_arrow/__init__.py b/weave/legacy/weave/ops_arrow/__init__.py new file mode 100644 index 00000000000..980f2fd2ce6 --- /dev/null +++ b/weave/legacy/weave/ops_arrow/__init__.py @@ -0,0 +1,12 @@ +from weave.legacy.weave import context_state as _context_state + +_loading_builtins_token = _context_state.set_loading_built_ins() + +from weave.legacy.weave.arrow.arrow import * +from weave.legacy.weave.arrow.convert import * +from weave.legacy.weave.arrow.list_ import * +from weave.legacy.weave.ops_arrow import ops +from weave.legacy.weave.ops_arrow.list_range import range +from weave.legacy.weave.ops_arrow.vectorize import vectorize + +_context_state.clear_loading_built_ins(_loading_builtins_token) diff --git a/weave/legacy/ops_arrow/arraylist_ops.py b/weave/legacy/weave/ops_arrow/arraylist_ops.py similarity index 96% rename from weave/legacy/ops_arrow/arraylist_ops.py rename to weave/legacy/weave/ops_arrow/arraylist_ops.py index cd0d4752f94..209cc8b0813 100644 --- a/weave/legacy/ops_arrow/arraylist_ops.py +++ b/weave/legacy/weave/ops_arrow/arraylist_ops.py @@ -3,11 +3,11 @@ import pyarrow as pa from pyarrow import compute as pc -from weave.legacy import weave_types as types -from weave.legacy.arrow.arrow import ArrowWeaveListType, arrow_as_array -from weave.legacy.arrow.list_ import ArrowWeaveList -from weave.legacy.decorator_arrow_op import arrow_op -from weave.legacy.language_features.tagging import tagged_value_type +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.arrow.arrow import ArrowWeaveListType, arrow_as_array +from weave.legacy.weave.arrow.list_ import ArrowWeaveList +from weave.legacy.weave.decorator_arrow_op import arrow_op +from weave.legacy.weave.language_features.tagging import tagged_value_type def _arrowweavelistlist_listindex_output_type(input_types): @@ -70,7 +70,7 @@ def listindex(self, index): def _list_op_output_object_type(input_types): self_type = input_types["self"] - from weave.legacy import op_def + from weave.legacy.weave import op_def def _remove_tags(t): if isinstance(t, tagged_value_type.TaggedValueType): diff --git a/weave/legacy/ops_arrow/boolean.py b/weave/legacy/weave/ops_arrow/boolean.py similarity index 93% rename from weave/legacy/ops_arrow/boolean.py rename to weave/legacy/weave/ops_arrow/boolean.py index 68b09327c61..5131f10544b 100644 --- a/weave/legacy/ops_arrow/boolean.py +++ b/weave/legacy/weave/ops_arrow/boolean.py @@ -1,11 +1,11 @@ import pyarrow as pa import pyarrow.compute as pc -from weave.legacy import weave_types as types -from weave.legacy.arrow.list_ import ArrowWeaveList, ArrowWeaveListType -from weave.legacy.decorator_arrow_op import arrow_op -from weave.legacy.decorator_op import op -from weave.legacy.ops_arrow import util +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.arrow.list_ import ArrowWeaveList, ArrowWeaveListType +from weave.legacy.weave.decorator_arrow_op import arrow_op +from weave.legacy.weave.decorator_op import op +from weave.legacy.weave.ops_arrow import util unary_input_type = { "self": ArrowWeaveListType(types.Boolean()), diff --git a/weave/legacy/ops_arrow/convert_ops.py b/weave/legacy/weave/ops_arrow/convert_ops.py similarity index 71% rename from weave/legacy/ops_arrow/convert_ops.py rename to weave/legacy/weave/ops_arrow/convert_ops.py index b2538128b42..d67eaa348c4 100644 --- a/weave/legacy/ops_arrow/convert_ops.py +++ b/weave/legacy/weave/ops_arrow/convert_ops.py @@ -1,7 +1,7 @@ -from weave.legacy import weave_types as types -from weave.legacy.api import op -from weave.legacy.arrow import convert -from weave.legacy.arrow.arrow import ArrowWeaveListType +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.api import op +from weave.legacy.weave.arrow import convert +from weave.legacy.weave.arrow.arrow import ArrowWeaveListType @op( diff --git a/weave/legacy/ops_arrow/date.py b/weave/legacy/weave/ops_arrow/date.py similarity index 94% rename from weave/legacy/ops_arrow/date.py rename to weave/legacy/weave/ops_arrow/date.py index 9d93ee161d6..ce84f02cb52 100644 --- a/weave/legacy/ops_arrow/date.py +++ b/weave/legacy/weave/ops_arrow/date.py @@ -3,12 +3,12 @@ import pyarrow as pa import pyarrow.compute as pc -from weave.legacy import weave_types as types -from weave.legacy.api import op -from weave.legacy import timestamp as weave_timestamp -from weave.legacy.arrow.list_ import ArrowWeaveList, ArrowWeaveListType -from weave.legacy.decorator_arrow_op import arrow_op -from weave.legacy.ops_arrow import util +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.api import op +from weave.legacy.weave import timestamp as weave_timestamp +from weave.legacy.weave.arrow.list_ import ArrowWeaveList, ArrowWeaveListType +from weave.legacy.weave.decorator_arrow_op import arrow_op +from weave.legacy.weave.ops_arrow import util ARROW_WEAVE_LIST_TIMESTAMP_TYPE = ArrowWeaveListType(types.Timestamp()) ARROW_WEAVE_LIST_BOOLEAN_TYPE = ArrowWeaveListType(types.Boolean()) diff --git a/weave/legacy/ops_arrow/dict.py b/weave/legacy/weave/ops_arrow/dict.py similarity index 96% rename from weave/legacy/ops_arrow/dict.py rename to weave/legacy/weave/ops_arrow/dict.py index 996ef2e4ffc..883d2295a43 100644 --- a/weave/legacy/ops_arrow/dict.py +++ b/weave/legacy/weave/ops_arrow/dict.py @@ -3,23 +3,23 @@ import numpy as np import pyarrow as pa -from weave.legacy import weave_types as types -from weave.legacy import _dict_utils -from weave.legacy.arrow import convert -from weave.legacy.arrow.arrow import arrow_as_array, offsets_starting_at_zero -from weave.legacy.arrow.arrow_tags import direct_add_arrow_tags -from weave.legacy.arrow.constructors import ( +from weave.legacy.weave import weave_types as types +from weave.legacy.weave import _dict_utils +from weave.legacy.weave.arrow import convert +from weave.legacy.weave.arrow.arrow import arrow_as_array, offsets_starting_at_zero +from weave.legacy.weave.arrow.arrow_tags import direct_add_arrow_tags +from weave.legacy.weave.arrow.constructors import ( vectorized_container_constructor_preprocessor, vectorized_input_types, ) -from weave.legacy.arrow.list_ import ArrowWeaveList, ArrowWeaveListType -from weave.legacy.decorator_arrow_op import arrow_op -from weave.legacy.decorator_op import op -from weave.legacy.language_features.tagging import ( +from weave.legacy.weave.arrow.list_ import ArrowWeaveList, ArrowWeaveListType +from weave.legacy.weave.decorator_arrow_op import arrow_op +from weave.legacy.weave.decorator_op import op +from weave.legacy.weave.language_features.tagging import ( process_opdef_output_type, ) -from weave.legacy.op_args import OpVarArgs -from weave.legacy.ops_primitives import projection_utils +from weave.legacy.weave.op_args import OpVarArgs +from weave.legacy.weave.ops_primitives import projection_utils def typeddict_pick_output_type(input_types): diff --git a/weave/legacy/ops_arrow/list_join.py b/weave/legacy/weave/ops_arrow/list_join.py similarity index 93% rename from weave/legacy/ops_arrow/list_join.py rename to weave/legacy/weave/ops_arrow/list_join.py index 1f026ab61e9..2ba1ed3f158 100644 --- a/weave/legacy/ops_arrow/list_join.py +++ b/weave/legacy/weave/ops_arrow/list_join.py @@ -4,24 +4,24 @@ import pyarrow as pa import pyarrow.compute as pc -from weave.legacy import engine_trace -from weave.legacy import weave_types as types -from weave.legacy.api import op -from weave.legacy import graph -from weave.legacy.arrow import convert -from weave.legacy.arrow.arrow import ( +from weave.legacy.weave import engine_trace +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.api import op +from weave.legacy.weave import graph +from weave.legacy.weave.arrow import convert +from weave.legacy.weave.arrow.arrow import ( ArrowWeaveListType, safe_coalesce, ) -from weave.legacy.arrow.arrow_tags import pushdown_list_tags -from weave.legacy.arrow.list_ import ( +from weave.legacy.weave.arrow.arrow_tags import pushdown_list_tags +from weave.legacy.weave.arrow.list_ import ( ArrowWeaveList, awl_zip, make_vec_dict, make_vec_taggedvalue, ) -from weave.legacy.language_features.tagging import tagged_value_type -from weave.legacy.ops_primitives import list_ as primitive_list +from weave.legacy.weave.language_features.tagging import tagged_value_type +from weave.legacy.weave.ops_primitives import list_ as primitive_list tracer = engine_trace.tracer() # type: ignore diff --git a/weave/legacy/ops_arrow/list_ops.py b/weave/legacy/weave/ops_arrow/list_ops.py similarity index 97% rename from weave/legacy/ops_arrow/list_ops.py rename to weave/legacy/weave/ops_arrow/list_ops.py index 1f034738bf9..7fabe4421d4 100644 --- a/weave/legacy/ops_arrow/list_ops.py +++ b/weave/legacy/weave/ops_arrow/list_ops.py @@ -7,34 +7,34 @@ import pyarrow as pa import pyarrow.compute as pc -from weave.legacy import weave_types as types -from weave.legacy.api import op, type_of -from weave.legacy import op_args, op_def -from weave.legacy.arrow import arrow_tags, convert -from weave.legacy.arrow.arrow import ( +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.api import op, type_of +from weave.legacy.weave import op_args, op_def +from weave.legacy.weave.arrow import arrow_tags, convert +from weave.legacy.weave.arrow.arrow import ( ArrowWeaveListType, arrow_as_array, offsets_starting_at_zero, ) -from weave.legacy.arrow.concat import concatenate_all -from weave.legacy.arrow.constructors import ( +from weave.legacy.weave.arrow.concat import concatenate_all +from weave.legacy.weave.arrow.constructors import ( vectorized_container_constructor_preprocessor, vectorized_input_types, ) -from weave.legacy.arrow.convert import to_compare_safe -from weave.legacy.arrow.list_ import ( +from weave.legacy.weave.arrow.convert import to_compare_safe +from weave.legacy.weave.arrow.list_ import ( ArrowWeaveList, PathType, is_list_arrowweavelist, is_taggedvalue_arrowweavelist, ) -from weave.legacy.decorator_arrow_op import arrow_op -from weave.legacy.language_features.tagging import ( +from weave.legacy.weave.decorator_arrow_op import arrow_op +from weave.legacy.weave.language_features.tagging import ( tagged_value_type, tagged_value_type_helpers, ) -from weave.legacy.ops_arrow.vectorize import _apply_fn_node_with_tag_pushdown -from weave.legacy.ops_primitives import list_ as primitive_list +from weave.legacy.weave.ops_arrow.vectorize import _apply_fn_node_with_tag_pushdown +from weave.legacy.weave.ops_primitives import list_ as primitive_list FLATTEN_DELIMITER = "➡️" @@ -961,7 +961,7 @@ def flatten(arr): def _drop_tags_output_type(input_type): - from weave.legacy.op_def import map_type + from weave.legacy.weave.op_def import map_type return map_type( input_type["arr"], diff --git a/weave/legacy/ops_arrow/list_range.py b/weave/legacy/weave/ops_arrow/list_range.py similarity index 61% rename from weave/legacy/ops_arrow/list_range.py rename to weave/legacy/weave/ops_arrow/list_range.py index 064e8e29a22..3783eaa39b7 100644 --- a/weave/legacy/ops_arrow/list_range.py +++ b/weave/legacy/weave/ops_arrow/list_range.py @@ -2,9 +2,9 @@ import pyarrow as pa -from weave.legacy import weave_types as types -from weave.legacy.api import op -from weave.legacy.arrow.list_ import ArrowWeaveList +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.api import op +from weave.legacy.weave.arrow.list_ import ArrowWeaveList py_range = range diff --git a/weave/legacy/ops_arrow/number.py b/weave/legacy/weave/ops_arrow/number.py similarity index 97% rename from weave/legacy/ops_arrow/number.py rename to weave/legacy/weave/ops_arrow/number.py index 071b4d37ec9..2d424fd08e6 100644 --- a/weave/legacy/ops_arrow/number.py +++ b/weave/legacy/weave/ops_arrow/number.py @@ -4,12 +4,12 @@ import pyarrow as pa import pyarrow.compute as pc -from weave.legacy import weave_types as types -from weave.legacy.api import op -from weave.legacy import timestamp as weave_timestamp -from weave.legacy.arrow.list_ import ArrowWeaveList, ArrowWeaveListType -from weave.legacy.decorator_arrow_op import arrow_op -from weave.legacy.ops_arrow import util +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.api import op +from weave.legacy.weave import timestamp as weave_timestamp +from weave.legacy.weave.arrow.list_ import ArrowWeaveList, ArrowWeaveListType +from weave.legacy.weave.decorator_arrow_op import arrow_op +from weave.legacy.weave.ops_arrow import util ARROW_WEAVE_LIST_NUMBER_TYPE = ArrowWeaveListType(types.Number()) ARROW_WEAVE_LIST_BOOLEAN_TYPE = ArrowWeaveListType(types.Boolean()) diff --git a/weave/legacy/ops_arrow/obj.py b/weave/legacy/weave/ops_arrow/obj.py similarity index 72% rename from weave/legacy/ops_arrow/obj.py rename to weave/legacy/weave/ops_arrow/obj.py index 7e38e90297b..b94fd1ef576 100644 --- a/weave/legacy/ops_arrow/obj.py +++ b/weave/legacy/weave/ops_arrow/obj.py @@ -1,7 +1,7 @@ -from weave.legacy import weave_types as types -from weave.legacy.arrow.list_ import ArrowWeaveList, ArrowWeaveListType -from weave.legacy.decorator_arrow_op import arrow_op -from weave.legacy.ops_primitives import obj as primitives_obj +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.arrow.list_ import ArrowWeaveList, ArrowWeaveListType +from weave.legacy.weave.decorator_arrow_op import arrow_op +from weave.legacy.weave.ops_primitives import obj as primitives_obj @arrow_op( diff --git a/weave/legacy/weave/ops_arrow/ops.py b/weave/legacy/weave/ops_arrow/ops.py new file mode 100644 index 00000000000..452bebd2a93 --- /dev/null +++ b/weave/legacy/weave/ops_arrow/ops.py @@ -0,0 +1,11 @@ +from weave.legacy.weave.ops_arrow.arraylist_ops import * +from weave.legacy.weave.ops_arrow.boolean import * +from weave.legacy.weave.ops_arrow.convert_ops import * +from weave.legacy.weave.ops_arrow.date import * +from weave.legacy.weave.ops_arrow.dict import * +from weave.legacy.weave.ops_arrow.list_join import * +from weave.legacy.weave.ops_arrow.list_ops import * # type: ignore +from weave.legacy.weave.ops_arrow.number import * +from weave.legacy.weave.ops_arrow.obj import * +from weave.legacy.weave.ops_arrow.ref_ops import * +from weave.legacy.weave.ops_arrow.string import * diff --git a/weave/legacy/ops_arrow/ref_ops.py b/weave/legacy/weave/ops_arrow/ref_ops.py similarity index 83% rename from weave/legacy/ops_arrow/ref_ops.py rename to weave/legacy/weave/ops_arrow/ref_ops.py index d40ee05949c..9ea8f800bab 100644 --- a/weave/legacy/ops_arrow/ref_ops.py +++ b/weave/legacy/weave/ops_arrow/ref_ops.py @@ -1,11 +1,11 @@ import pyarrow as pa import pyarrow.compute as pc -from weave.legacy import weave_types as types -from weave.legacy.arrow.list_ import ArrowWeaveList, ArrowWeaveListType -from weave.legacy.decorator_arrow_op import arrow_op -from weave.legacy.decorator_op import op -from weave.legacy.ops_arrow import util +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.arrow.list_ import ArrowWeaveList, ArrowWeaveListType +from weave.legacy.weave.decorator_arrow_op import arrow_op +from weave.legacy.weave.decorator_op import op +from weave.legacy.weave.ops_arrow import util nullable_binary_input_type = { "self": ArrowWeaveListType(types.optional(types.RefType())), @@ -22,7 +22,7 @@ output_type=ArrowWeaveListType(types.Boolean()), ) def ref_equal(self, other): - from weave.legacy import storage + from weave.legacy.weave import storage # Weave engine automatically derefs, so we need to undo that via # _get_ref here. diff --git a/weave/legacy/ops_arrow/string.py b/weave/legacy/weave/ops_arrow/string.py similarity index 97% rename from weave/legacy/ops_arrow/string.py rename to weave/legacy/weave/ops_arrow/string.py index 491ad640a6e..48a8d3f8c19 100644 --- a/weave/legacy/ops_arrow/string.py +++ b/weave/legacy/weave/ops_arrow/string.py @@ -3,12 +3,12 @@ import pyarrow as pa import pyarrow.compute as pc -from weave.legacy import weave_types as types -from weave.legacy.api import op -from weave.legacy.arrow.arrow import ArrowWeaveListType, offsets_starting_at_zero -from weave.legacy.arrow.list_ import ArrowWeaveList, ArrowWeaveListType -from weave.legacy.decorator_arrow_op import arrow_op -from weave.legacy.ops_arrow import util +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.api import op +from weave.legacy.weave.arrow.arrow import ArrowWeaveListType, offsets_starting_at_zero +from weave.legacy.weave.arrow.list_ import ArrowWeaveList, ArrowWeaveListType +from weave.legacy.weave.decorator_arrow_op import arrow_op +from weave.legacy.weave.ops_arrow import util ARROW_WEAVE_LIST_STRING_TYPE = ArrowWeaveListType(types.String()) ARROW_WEAVE_LIST_BOOLEAN_TYPE = ArrowWeaveListType(types.Boolean()) diff --git a/weave/legacy/ops_arrow/util.py b/weave/legacy/weave/ops_arrow/util.py similarity index 91% rename from weave/legacy/ops_arrow/util.py rename to weave/legacy/weave/ops_arrow/util.py index 5d515d9984a..97b12773d82 100644 --- a/weave/legacy/ops_arrow/util.py +++ b/weave/legacy/weave/ops_arrow/util.py @@ -3,15 +3,15 @@ import pyarrow as pa from pyarrow import compute as pc -from weave.legacy import weave_types as types -from weave.legacy import graph +from weave.legacy.weave import weave_types as types +from weave.legacy.weave import graph # Reimplementation of Weave0 `toSafeCall` which # converts media to their digest def _to_compare_safe_call(node: graph.OutputNode) -> graph.OutputNode: - from weave.legacy.ops_domain.wbmedia import ArtifactAssetType - from weave.legacy.ops_primitives.dict import dict_ + from weave.legacy.weave.ops_domain.wbmedia import ArtifactAssetType + from weave.legacy.weave.ops_primitives.dict import dict_ node_type = types.non_none(node.type) if ArtifactAssetType.assign_type(node_type): diff --git a/weave/legacy/ops_arrow/vectorize.py b/weave/legacy/weave/ops_arrow/vectorize.py similarity index 97% rename from weave/legacy/ops_arrow/vectorize.py rename to weave/legacy/weave/ops_arrow/vectorize.py index 107666929e1..fedd1cc5305 100644 --- a/weave/legacy/ops_arrow/vectorize.py +++ b/weave/legacy/weave/ops_arrow/vectorize.py @@ -6,21 +6,18 @@ import numpy as np import pyarrow as pa -from weave import ( - - weave_internal, -) -from weave.legacy import errors,weavify, registry_mem -from weave.legacy import weave_types as types -from weave.legacy.api import op, use -from weave.legacy import dispatch, graph, graph_debug, op_args, op_def -from weave.legacy.arrow import convert -from weave.legacy.arrow.arrow import ArrowWeaveListType -from weave.legacy.arrow.arrow_tags import pushdown_list_tags -from weave.legacy.arrow.list_ import ArrowWeaveList -from weave.legacy.ops_arrow import arraylist_ops, convert_ops -from weave.legacy.ops_arrow.dict import preprocess_merge -from weave.legacy.ops_primitives.dict import dict_ +from weave.legacy.weave import weave_internal +from weave.legacy.weave import errors,weavify, registry_mem +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.api import op, use +from weave.legacy.weave import dispatch, graph, graph_debug, op_args, op_def +from weave.legacy.weave.arrow import convert +from weave.legacy.weave.arrow.arrow import ArrowWeaveListType +from weave.legacy.weave.arrow.arrow_tags import pushdown_list_tags +from weave.legacy.weave.arrow.list_ import ArrowWeaveList +from weave.legacy.weave.ops_arrow import arraylist_ops, convert_ops +from weave.legacy.weave.ops_arrow.dict import preprocess_merge +from weave.legacy.weave.ops_primitives.dict import dict_ class VectorizeError(errors.WeaveBaseError): @@ -76,7 +73,7 @@ def _create_manually_mapped_op( mapped_inputs = {k: v for k, v in inputs.items() if k in vectorized_keys} rest_inputs = {k: v for k, v in inputs.items() if k not in vectorized_keys} - from weave.legacy.ops_arrow import dict + from weave.legacy.weave.ops_arrow import dict input_arr = dict.arrow_dict_(**mapped_inputs).to_py() @@ -646,7 +643,7 @@ def raise_on_python_bailout(): def _call_and_ensure_awl( awl: ArrowWeaveList, called: graph.OutputNode ) -> ArrowWeaveList: - from weave.legacy import compile + from weave.legacy.weave import compile with compile.disable_compile(): res = use(called) @@ -729,7 +726,7 @@ def _apply_fn_node(awl: ArrowWeaveList, fn: graph.OutputNode) -> ArrowWeaveList: ) return convert.to_arrow([], types.List(fn.type), artifact=awl._artifact) - from weave.legacy import execute_fast + from weave.legacy.weave import execute_fast fn = execute_fast._resolve_static_branches(fn) logging.info("Vectorizing. Static branch resolution complete.: %s", debug_str) diff --git a/weave/legacy/ops_domain/__init__.py b/weave/legacy/weave/ops_domain/__init__.py similarity index 93% rename from weave/legacy/ops_domain/__init__.py rename to weave/legacy/weave/ops_domain/__init__.py index c5760cdd35a..7cdc4cfcefb 100644 --- a/weave/legacy/ops_domain/__init__.py +++ b/weave/legacy/weave/ops_domain/__init__.py @@ -1,4 +1,4 @@ -from weave.legacy import context_state as _context_state +from weave.legacy.weave import context_state as _context_state _loading_builtins_token = _context_state.set_loading_built_ins() diff --git a/weave/legacy/ops_domain/artifact_alias_ops.py b/weave/legacy/weave/ops_domain/artifact_alias_ops.py similarity index 61% rename from weave/legacy/ops_domain/artifact_alias_ops.py rename to weave/legacy/weave/ops_domain/artifact_alias_ops.py index 140ffadfbaf..01acd1d6537 100644 --- a/weave/legacy/ops_domain/artifact_alias_ops.py +++ b/weave/legacy/weave/ops_domain/artifact_alias_ops.py @@ -1,11 +1,11 @@ -from weave.legacy import weave_types as types -from weave.legacy.api import op -from weave.legacy.gql_op_plugin import wb_gql_op_plugin -from weave.legacy.language_features.tagging.make_tag_getter_op import ( +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.api import op +from weave.legacy.weave.gql_op_plugin import wb_gql_op_plugin +from weave.legacy.weave.language_features.tagging.make_tag_getter_op import ( make_tag_getter_op, ) -from weave.legacy.ops_domain import wb_domain_types as wdt -from weave.legacy.ops_domain.wandb_domain_gql import ( +from weave.legacy.weave.ops_domain import wb_domain_types as wdt +from weave.legacy.weave.ops_domain.wandb_domain_gql import ( gql_connection_op, gql_direct_edge_op, gql_prop_op, diff --git a/weave/legacy/ops_domain/artifact_collection_ops.py b/weave/legacy/weave/ops_domain/artifact_collection_ops.py similarity index 94% rename from weave/legacy/ops_domain/artifact_collection_ops.py rename to weave/legacy/weave/ops_domain/artifact_collection_ops.py index aaf6231a37e..ccd8dc913ea 100644 --- a/weave/legacy/ops_domain/artifact_collection_ops.py +++ b/weave/legacy/weave/ops_domain/artifact_collection_ops.py @@ -1,12 +1,12 @@ import typing import urllib -from weave.legacy import errors -from weave.legacy import weave_types as types -from weave.legacy.api import op -from weave.legacy.gql_op_plugin import wb_gql_op_plugin -from weave.legacy.ops_domain import wb_domain_types as wdt -from weave.legacy.ops_domain.wandb_domain_gql import ( +from weave.legacy.weave import errors +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.api import op +from weave.legacy.weave.gql_op_plugin import wb_gql_op_plugin +from weave.legacy.weave.ops_domain import wb_domain_types as wdt +from weave.legacy.weave.ops_domain.wandb_domain_gql import ( gql_connection_op, gql_direct_edge_op, gql_prop_op, diff --git a/weave/legacy/ops_domain/artifact_membership_ops.py b/weave/legacy/weave/ops_domain/artifact_membership_ops.py similarity index 89% rename from weave/legacy/ops_domain/artifact_membership_ops.py rename to weave/legacy/weave/ops_domain/artifact_membership_ops.py index ab042265304..780ec6f93ac 100644 --- a/weave/legacy/ops_domain/artifact_membership_ops.py +++ b/weave/legacy/weave/ops_domain/artifact_membership_ops.py @@ -1,10 +1,10 @@ import urllib -from weave.legacy import weave_types as types -from weave.legacy.api import op -from weave.legacy.gql_op_plugin import wb_gql_op_plugin -from weave.legacy.ops_domain import wb_domain_types as wdt -from weave.legacy.ops_domain.wandb_domain_gql import ( +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.api import op +from weave.legacy.weave.gql_op_plugin import wb_gql_op_plugin +from weave.legacy.weave.ops_domain import wb_domain_types as wdt +from weave.legacy.weave.ops_domain.wandb_domain_gql import ( gql_direct_edge_op, gql_prop_op, ) diff --git a/weave/legacy/ops_domain/artifact_type_ops.py b/weave/legacy/weave/ops_domain/artifact_type_ops.py similarity index 89% rename from weave/legacy/ops_domain/artifact_type_ops.py rename to weave/legacy/weave/ops_domain/artifact_type_ops.py index 4b62b70929e..64a81170436 100644 --- a/weave/legacy/ops_domain/artifact_type_ops.py +++ b/weave/legacy/weave/ops_domain/artifact_type_ops.py @@ -1,10 +1,10 @@ import typing -from weave.legacy import weave_types as types -from weave.legacy.api import op -from weave.legacy.gql_op_plugin import wb_gql_op_plugin -from weave.legacy.ops_domain import wb_domain_types as wdt -from weave.legacy.ops_domain.wandb_domain_gql import ( +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.api import op +from weave.legacy.weave.gql_op_plugin import wb_gql_op_plugin +from weave.legacy.weave.ops_domain import wb_domain_types as wdt +from weave.legacy.weave.ops_domain.wandb_domain_gql import ( _make_alias, gql_connection_op, gql_direct_edge_op, diff --git a/weave/legacy/ops_domain/artifact_version_ops.py b/weave/legacy/weave/ops_domain/artifact_version_ops.py similarity index 96% rename from weave/legacy/ops_domain/artifact_version_ops.py rename to weave/legacy/weave/ops_domain/artifact_version_ops.py index aa816e3f716..e9b8136c6f3 100644 --- a/weave/legacy/ops_domain/artifact_version_ops.py +++ b/weave/legacy/weave/ops_domain/artifact_version_ops.py @@ -2,14 +2,14 @@ import typing from urllib.parse import quote -from weave.legacy import errors -from weave.legacy import weave_types as types -from weave.legacy.api import op -from weave.legacy import artifact_fs, artifact_wandb, input_provider -from weave.legacy.gql_op_plugin import wb_gql_op_plugin -from weave.legacy.ops_domain import wb_domain_types as wdt -from weave.legacy.ops_domain import wb_util -from weave.legacy.ops_domain.wandb_domain_gql import ( +from weave.legacy.weave import errors +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.api import op +from weave.legacy.weave import artifact_fs, artifact_wandb, input_provider +from weave.legacy.weave.gql_op_plugin import wb_gql_op_plugin +from weave.legacy.weave.ops_domain import wb_domain_types as wdt +from weave.legacy.weave.ops_domain import wb_util +from weave.legacy.weave.ops_domain.wandb_domain_gql import ( _make_alias, gql_connection_op, gql_direct_edge_op, @@ -460,10 +460,10 @@ def artifact_version_weave_type( def _get_history_metrics( artifactVersion: wdt.ArtifactVersion, ) -> dict[str, typing.Any]: - from weave import weave_internal - from weave.legacy.compile import enable_compile - from weave.legacy.graph import ConstNode, OutputNode - from weave.legacy.ops_domain import wb_domain_types + from weave.legacy.weave import weave_internal + from weave.legacy.weave.compile import enable_compile + from weave.legacy.weave.graph import ConstNode, OutputNode + from weave.legacy.weave.ops_domain import wb_domain_types created_by = artifactVersion["createdBy"] if created_by == None or created_by["__typename"] != "Run": diff --git a/weave/legacy/ops_domain/entity_ops.py b/weave/legacy/weave/ops_domain/entity_ops.py similarity index 82% rename from weave/legacy/ops_domain/entity_ops.py rename to weave/legacy/weave/ops_domain/entity_ops.py index 89b45216863..08c433262a9 100644 --- a/weave/legacy/ops_domain/entity_ops.py +++ b/weave/legacy/weave/ops_domain/entity_ops.py @@ -1,11 +1,11 @@ -from weave.legacy import weave_types as types -from weave.legacy.api import op -from weave.legacy.gql_op_plugin import wb_gql_op_plugin -from weave.legacy.language_features.tagging.make_tag_getter_op import ( +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.api import op +from weave.legacy.weave.gql_op_plugin import wb_gql_op_plugin +from weave.legacy.weave.language_features.tagging.make_tag_getter_op import ( make_tag_getter_op, ) -from weave.legacy.ops_domain import wb_domain_types as wdt -from weave.legacy.ops_domain.wandb_domain_gql import ( +from weave.legacy.weave.ops_domain import wb_domain_types as wdt +from weave.legacy.weave.ops_domain.wandb_domain_gql import ( gql_connection_op, gql_direct_edge_op, gql_prop_op, diff --git a/weave/legacy/ops_domain/org_ops.py b/weave/legacy/weave/ops_domain/org_ops.py similarity index 85% rename from weave/legacy/ops_domain/org_ops.py rename to weave/legacy/weave/ops_domain/org_ops.py index c0ce7588ba0..867bc2d299a 100644 --- a/weave/legacy/ops_domain/org_ops.py +++ b/weave/legacy/weave/ops_domain/org_ops.py @@ -1,6 +1,6 @@ -from weave.legacy import weave_types as types -from weave.legacy.ops_domain import wb_domain_types as wdt -from weave.legacy.ops_domain.wandb_domain_gql import ( +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.ops_domain import wb_domain_types as wdt +from weave.legacy.weave.ops_domain.wandb_domain_gql import ( gql_connection_op, gql_direct_edge_op, gql_prop_op, diff --git a/weave/legacy/ops_domain/project_ops.py b/weave/legacy/weave/ops_domain/project_ops.py similarity index 93% rename from weave/legacy/ops_domain/project_ops.py rename to weave/legacy/weave/ops_domain/project_ops.py index b4463818c8a..297fea610b6 100644 --- a/weave/legacy/ops_domain/project_ops.py +++ b/weave/legacy/weave/ops_domain/project_ops.py @@ -1,16 +1,16 @@ import json import typing -from weave.legacy import errors -from weave.legacy import weave_types as types -from weave.legacy.api import op -from weave.legacy import input_provider -from weave.legacy.gql_op_plugin import wb_gql_op_plugin -from weave.legacy.language_features.tagging.make_tag_getter_op import ( +from weave.legacy.weave import errors +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.api import op +from weave.legacy.weave import input_provider +from weave.legacy.weave.gql_op_plugin import wb_gql_op_plugin +from weave.legacy.weave.language_features.tagging.make_tag_getter_op import ( make_tag_getter_op, ) -from weave.legacy.ops_domain import wb_domain_types as wdt -from weave.legacy.ops_domain.wandb_domain_gql import ( +from weave.legacy.weave.ops_domain import wb_domain_types as wdt +from weave.legacy.weave.ops_domain.wandb_domain_gql import ( gql_connection_op, gql_direct_edge_op, gql_prop_op, diff --git a/weave/legacy/ops_domain/repo_insight_ops.py b/weave/legacy/weave/ops_domain/repo_insight_ops.py similarity index 94% rename from weave/legacy/ops_domain/repo_insight_ops.py rename to weave/legacy/weave/ops_domain/repo_insight_ops.py index 5baa89dec29..3ff32d81e1d 100644 --- a/weave/legacy/ops_domain/repo_insight_ops.py +++ b/weave/legacy/weave/ops_domain/repo_insight_ops.py @@ -1,12 +1,12 @@ import datetime import json -from weave.legacy import errors -from weave.legacy import weave_types as types -from weave.legacy.api import op -from weave.legacy.gql_json_cache import use_json -from weave.legacy.gql_op_plugin import wb_gql_op_plugin -from weave.legacy.ops_domain.wandb_domain_gql import ( +from weave.legacy.weave import errors +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.api import op +from weave.legacy.weave.gql_json_cache import use_json +from weave.legacy.weave.gql_op_plugin import wb_gql_op_plugin +from weave.legacy.weave.ops_domain.wandb_domain_gql import ( _make_alias, ) diff --git a/weave/legacy/ops_domain/report_ops.py b/weave/legacy/weave/ops_domain/report_ops.py similarity index 91% rename from weave/legacy/ops_domain/report_ops.py rename to weave/legacy/weave/ops_domain/report_ops.py index f0f0db1a61d..011768ac1f9 100644 --- a/weave/legacy/ops_domain/report_ops.py +++ b/weave/legacy/weave/ops_domain/report_ops.py @@ -2,12 +2,12 @@ import typing import urllib -from weave.legacy import errors -from weave.legacy import weave_types as types -from weave.legacy.api import op -from weave.legacy.gql_op_plugin import wb_gql_op_plugin -from weave.legacy.ops_domain import wb_domain_types as wdt -from weave.legacy.ops_domain.wandb_domain_gql import ( +from weave.legacy.weave import errors +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.api import op +from weave.legacy.weave.gql_op_plugin import wb_gql_op_plugin +from weave.legacy.weave.ops_domain import wb_domain_types as wdt +from weave.legacy.weave.ops_domain.wandb_domain_gql import ( gql_direct_edge_op, gql_prop_op, ) diff --git a/weave/legacy/weave/ops_domain/run_history/__init__.py b/weave/legacy/weave/ops_domain/run_history/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/weave/legacy/ops_domain/run_history/context.py b/weave/legacy/weave/ops_domain/run_history/context.py similarity index 100% rename from weave/legacy/ops_domain/run_history/context.py rename to weave/legacy/weave/ops_domain/run_history/context.py diff --git a/weave/legacy/ops_domain/run_history/history_op_common.py b/weave/legacy/weave/ops_domain/run_history/history_op_common.py similarity index 94% rename from weave/legacy/ops_domain/run_history/history_op_common.py rename to weave/legacy/weave/ops_domain/run_history/history_op_common.py index 3712e488bfc..c38dad124b2 100644 --- a/weave/legacy/ops_domain/run_history/history_op_common.py +++ b/weave/legacy/weave/ops_domain/run_history/history_op_common.py @@ -4,9 +4,9 @@ import pyarrow as pa from pyarrow import parquet as pq -from weave.legacy import weave_types as types -from weave.legacy.api import use -from weave.legacy import ( +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.api import use +from weave.legacy.weave import ( util, errors, engine_trace, @@ -21,17 +21,17 @@ io_service, op_args, ) -from weave.legacy.arrow.concat import concatenate_all -from weave.legacy.compile_domain import InputAndStitchProvider -from weave.legacy.compile_table import KeyTree -from weave.legacy.language_features.tagging.tagged_value_type import TaggedValueType -from weave.legacy.mappers_arrow import map_to_arrow -from weave.legacy.ops_arrow import ArrowWeaveList -from weave.legacy.ops_arrow.list_ops import concat -from weave.legacy.ops_domain import table, wb_util -from weave.legacy.ops_domain import wb_domain_types as wdt -from weave.legacy.ops_primitives import make_list -from weave.legacy.wandb_interface import wandb_stream_table +from weave.legacy.weave.arrow.concat import concatenate_all +from weave.legacy.weave.compile_domain import InputAndStitchProvider +from weave.legacy.weave.compile_table import KeyTree +from weave.legacy.weave.language_features.tagging.tagged_value_type import TaggedValueType +from weave.legacy.weave.mappers_arrow import map_to_arrow +from weave.legacy.weave.ops_arrow import ArrowWeaveList +from weave.legacy.weave.ops_arrow.list_ops import concat +from weave.legacy.weave.ops_domain import table, wb_util +from weave.legacy.weave.ops_domain import wb_domain_types as wdt +from weave.legacy.weave.ops_primitives import make_list +from weave.legacy.weave.wandb_interface import wandb_stream_table tracer = engine_trace.tracer() @@ -45,8 +45,8 @@ class TypeCount(typing.TypedDict): def history_key_type_count_to_weave_type(tc: TypeCount) -> types.Type: - from weave.legacy.ops_domain.trace_tree import WBTraceTree - from weave.legacy.ops_domain.wbmedia import ImageArtifactFileRefType + from weave.legacy.weave.ops_domain.trace_tree import WBTraceTree + from weave.legacy.weave.ops_domain.wbmedia import ImageArtifactFileRefType tc_type = tc["type"] if tc_type == "string": diff --git a/weave/legacy/ops_domain/run_history/run_history_v1_legacy_ops.py b/weave/legacy/weave/ops_domain/run_history/run_history_v1_legacy_ops.py similarity index 89% rename from weave/legacy/ops_domain/run_history/run_history_v1_legacy_ops.py rename to weave/legacy/weave/ops_domain/run_history/run_history_v1_legacy_ops.py index d12822b462b..c78fe031a41 100644 --- a/weave/legacy/ops_domain/run_history/run_history_v1_legacy_ops.py +++ b/weave/legacy/weave/ops_domain/run_history/run_history_v1_legacy_ops.py @@ -2,14 +2,14 @@ import pyarrow as pa -from weave.legacy import engine_trace -from weave.legacy import weave_types as types -from weave.legacy.api import op -from weave.legacy import gql_json_cache -from weave.legacy.gql_op_plugin import wb_gql_op_plugin -from weave.legacy.ops_domain import wb_domain_types as wdt -from weave.legacy.ops_domain import wb_util -from weave.legacy.ops_domain.run_history import history_op_common +from weave.legacy.weave import engine_trace +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.api import op +from weave.legacy.weave import gql_json_cache +from weave.legacy.weave.gql_op_plugin import wb_gql_op_plugin +from weave.legacy.weave.ops_domain import wb_domain_types as wdt +from weave.legacy.weave.ops_domain import wb_util +from weave.legacy.weave.ops_domain.run_history import history_op_common tracer = engine_trace.tracer() diff --git a/weave/legacy/ops_domain/run_history/run_history_v2_parquet_media.py b/weave/legacy/weave/ops_domain/run_history/run_history_v2_parquet_media.py similarity index 90% rename from weave/legacy/ops_domain/run_history/run_history_v2_parquet_media.py rename to weave/legacy/weave/ops_domain/run_history/run_history_v2_parquet_media.py index db2c0a7a50f..113f9db6abe 100644 --- a/weave/legacy/ops_domain/run_history/run_history_v2_parquet_media.py +++ b/weave/legacy/weave/ops_domain/run_history/run_history_v2_parquet_media.py @@ -6,17 +6,17 @@ import pyarrow as pa -from weave.legacy import engine_trace -from weave.legacy import weave_types as types -from weave.legacy.api import op, use -from weave.legacy import artifact_mem, gql_json_cache -from weave.legacy.arrow import convert -from weave.legacy.arrow.list_ import ArrowWeaveList, ArrowWeaveListType -from weave.legacy.gql_op_plugin import wb_gql_op_plugin -from weave.legacy.ops_arrow.list_ops import concat -from weave.legacy.ops_domain import wb_domain_types as wdt -from weave.legacy.ops_domain import wb_util, wbmedia -from weave.legacy.ops_domain.run_history import history_op_common +from weave.legacy.weave import engine_trace +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.api import op, use +from weave.legacy.weave import artifact_mem, gql_json_cache +from weave.legacy.weave.arrow import convert +from weave.legacy.weave.arrow.list_ import ArrowWeaveList, ArrowWeaveListType +from weave.legacy.weave.gql_op_plugin import wb_gql_op_plugin +from weave.legacy.weave.ops_arrow.list_ops import concat +from weave.legacy.weave.ops_domain import wb_domain_types as wdt +from weave.legacy.weave.ops_domain import wb_util, wbmedia +from weave.legacy.weave.ops_domain.run_history import history_op_common tracer = engine_trace.tracer() diff --git a/weave/legacy/ops_domain/run_history/run_history_v3_parquet_stream_optimized.py b/weave/legacy/weave/ops_domain/run_history/run_history_v3_parquet_stream_optimized.py similarity index 98% rename from weave/legacy/ops_domain/run_history/run_history_v3_parquet_stream_optimized.py rename to weave/legacy/weave/ops_domain/run_history/run_history_v3_parquet_stream_optimized.py index 0b7750ddf1b..3d8dc2fe8d1 100644 --- a/weave/legacy/ops_domain/run_history/run_history_v3_parquet_stream_optimized.py +++ b/weave/legacy/weave/ops_domain/run_history/run_history_v3_parquet_stream_optimized.py @@ -5,10 +5,10 @@ import pyarrow as pa -from weave.legacy import errors -from weave.legacy import weave_types as types -from weave.legacy.api import op -from weave.legacy import ( +from weave.legacy.weave import errors +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.api import op +from weave.legacy.weave import ( engine_trace, artifact_base, artifact_fs, @@ -16,8 +16,8 @@ gql_json_cache, io_service, ) -from weave.legacy.arrow import convert -from weave.legacy.arrow.list_ import ( +from weave.legacy.weave.arrow import convert +from weave.legacy.weave.arrow.list_ import ( ArrowWeaveList, ArrowWeaveListType, PathItemType, @@ -25,16 +25,16 @@ make_vec_none, weave_arrow_type_check, ) -from weave.legacy.gql_op_plugin import wb_gql_op_plugin -from weave.legacy.op_def import map_type -from weave.legacy.ops_domain import trace_tree, wb_util, wbmedia -from weave.legacy.ops_domain import wb_domain_types as wdt -from weave.legacy.ops_domain.run_history import history_op_common -from weave.legacy.ops_domain.run_history.context import ( +from weave.legacy.weave.gql_op_plugin import wb_gql_op_plugin +from weave.legacy.weave.op_def import map_type +from weave.legacy.weave.ops_domain import trace_tree, wb_util, wbmedia +from weave.legacy.weave.ops_domain import wb_domain_types as wdt +from weave.legacy.weave.ops_domain.run_history import history_op_common +from weave.legacy.weave.ops_domain.run_history.context import ( get_error_on_non_vectorized_history_transform, ) -from weave.legacy.ops_domain.table import _patch_legacy_image_file_types -from weave.legacy.wandb_interface import wandb_stream_table +from weave.legacy.weave.ops_domain.table import _patch_legacy_image_file_types +from weave.legacy.weave.wandb_interface import wandb_stream_table tracer = engine_trace.tracer() diff --git a/weave/legacy/ops_domain/run_ops.py b/weave/legacy/weave/ops_domain/run_ops.py similarity index 95% rename from weave/legacy/ops_domain/run_ops.py rename to weave/legacy/weave/ops_domain/run_ops.py index 542f21fb665..e3bc71c970b 100644 --- a/weave/legacy/ops_domain/run_ops.py +++ b/weave/legacy/weave/ops_domain/run_ops.py @@ -37,26 +37,26 @@ import json import typing -from weave.legacy import engine_trace -from weave.legacy import weave_types as types -from weave.legacy.api import op -from weave.legacy import compile_table -from weave.legacy.gql_op_plugin import wb_gql_op_plugin -from weave.legacy.input_provider import InputAndStitchProvider -from weave.legacy.language_features.tagging.make_tag_getter_op import ( +from weave.legacy.weave import engine_trace +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.api import op +from weave.legacy.weave import compile_table +from weave.legacy.weave.gql_op_plugin import wb_gql_op_plugin +from weave.legacy.weave.input_provider import InputAndStitchProvider +from weave.legacy.weave.language_features.tagging.make_tag_getter_op import ( make_tag_getter_op, ) -from weave.legacy.ops_domain import wb_domain_types as wdt -from weave.legacy.ops_domain import wb_util +from weave.legacy.weave.ops_domain import wb_domain_types as wdt +from weave.legacy.weave.ops_domain import wb_util # Important to re-export ops -from weave.legacy.ops_domain.run_history import ( +from weave.legacy.weave.ops_domain.run_history import ( history_op_common, run_history_v1_legacy_ops, run_history_v2_parquet_media, run_history_v3_parquet_stream_optimized, ) -from weave.legacy.ops_domain.wandb_domain_gql import ( +from weave.legacy.weave.ops_domain.wandb_domain_gql import ( _make_alias, gql_connection_op, gql_direct_edge_op, diff --git a/weave/legacy/ops_domain/run_queue_ops.py b/weave/legacy/weave/ops_domain/run_queue_ops.py similarity index 51% rename from weave/legacy/ops_domain/run_queue_ops.py rename to weave/legacy/weave/ops_domain/run_queue_ops.py index 4211cfdb5cd..77af41ac69b 100644 --- a/weave/legacy/ops_domain/run_queue_ops.py +++ b/weave/legacy/weave/ops_domain/run_queue_ops.py @@ -1,13 +1,13 @@ import json -from weave.legacy import weave_types as types -from weave.legacy.api import op -from weave.legacy.gql_op_plugin import wb_gql_op_plugin -from weave.legacy.language_features.tagging.make_tag_getter_op import ( +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.api import op +from weave.legacy.weave.gql_op_plugin import wb_gql_op_plugin +from weave.legacy.weave.language_features.tagging.make_tag_getter_op import ( make_tag_getter_op, ) -from weave.legacy.ops_domain import wb_domain_types as wdt -from weave.legacy.ops_domain.wandb_domain_gql import ( +from weave.legacy.weave.ops_domain import wb_domain_types as wdt +from weave.legacy.weave.ops_domain.wandb_domain_gql import ( gql_connection_op, gql_direct_edge_op, gql_prop_op, diff --git a/weave/legacy/ops_domain/run_segment.py b/weave/legacy/weave/ops_domain/run_segment.py similarity index 91% rename from weave/legacy/ops_domain/run_segment.py rename to weave/legacy/weave/ops_domain/run_segment.py index 9dff1c01eaa..7924ba22340 100644 --- a/weave/legacy/ops_domain/run_segment.py +++ b/weave/legacy/weave/ops_domain/run_segment.py @@ -3,11 +3,11 @@ from typing import Optional, cast import weave -from weave.legacy import weave_types as types -from weave.legacy.api import Node, get, use -from weave.legacy import context_state as _context -from weave.legacy import panels -from weave.legacy.ops_arrow import ArrowWeaveList, ArrowWeaveListType +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.api import Node, get, use +from weave.legacy.weave import context_state as _context +from weave.legacy.weave import panels +from weave.legacy.weave.ops_arrow import ArrowWeaveList, ArrowWeaveListType _loading_builtins_token = _context.set_loading_built_ins() diff --git a/weave/legacy/ops_domain/stream_table_ops.py b/weave/legacy/weave/ops_domain/stream_table_ops.py similarity index 68% rename from weave/legacy/ops_domain/stream_table_ops.py rename to weave/legacy/weave/ops_domain/stream_table_ops.py index bc750f574e5..1aec716531b 100644 --- a/weave/legacy/ops_domain/stream_table_ops.py +++ b/weave/legacy/weave/ops_domain/stream_table_ops.py @@ -1,9 +1,9 @@ -from weave.legacy import weave_types -from weave.legacy.api import op -from weave.legacy import compile, op_def -from weave.legacy.arrow.arrow import ArrowWeaveListType -from weave.legacy.core_types import StreamTableType -from weave.legacy.ops_domain.project_ops import project +from weave.legacy.weave import weave_types +from weave.legacy.weave.api import op +from weave.legacy.weave import compile, op_def +from weave.legacy.weave.arrow.arrow import ArrowWeaveListType +from weave.legacy.weave.core_types import StreamTableType +from weave.legacy.weave.ops_domain.project_ops import project def _get_history_node(stream_table: StreamTableType): diff --git a/weave/legacy/ops_domain/table.py b/weave/legacy/weave/ops_domain/table.py similarity index 99% rename from weave/legacy/ops_domain/table.py rename to weave/legacy/weave/ops_domain/table.py index 7f4fbf91dae..b977104219d 100644 --- a/weave/legacy/ops_domain/table.py +++ b/weave/legacy/weave/ops_domain/table.py @@ -5,10 +5,10 @@ import logging import typing -from weave import weave_internal -from weave.legacy import weave_types as types -from weave.legacy.api import op, weave_class -from weave.legacy import ( +from weave.legacy.weave import weave_internal +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.api import op, weave_class +from weave.legacy.weave import ( artifact_fs, artifact_wandb, io_service, @@ -18,8 +18,8 @@ errors, util, ) -from weave.legacy import timestamp as weave_timestamp -from weave.legacy.ops_domain import trace_tree, wbmedia +from weave.legacy.weave import timestamp as weave_timestamp +from weave.legacy.weave.ops_domain import trace_tree, wbmedia @dataclasses.dataclass(frozen=True) diff --git a/weave/legacy/ops_domain/trace_tree.py b/weave/legacy/weave/ops_domain/trace_tree.py similarity index 97% rename from weave/legacy/ops_domain/trace_tree.py rename to weave/legacy/weave/ops_domain/trace_tree.py index 595916d1028..9ff5637f956 100644 --- a/weave/legacy/ops_domain/trace_tree.py +++ b/weave/legacy/weave/ops_domain/trace_tree.py @@ -11,11 +11,11 @@ from wandb.sdk.data_types.trace_tree import Result as WBSpanResult from wandb.sdk.data_types.trace_tree import Span as WBSpan -from weave.legacy import api as weave -from weave.legacy import stream_data_interfaces -from weave.legacy import weave_types as types -from weave.legacy import op_def -from weave.legacy.decorator_op import op +from weave.legacy.weave import api as weave +from weave.legacy.weave import stream_data_interfaces +from weave.legacy.weave import weave_types as types +from weave.legacy.weave import op_def +from weave.legacy.weave.decorator_op import op class StatusCode: diff --git a/weave/legacy/ops_domain/user_ops.py b/weave/legacy/weave/ops_domain/user_ops.py similarity index 77% rename from weave/legacy/ops_domain/user_ops.py rename to weave/legacy/weave/ops_domain/user_ops.py index 0c6527eea79..86e6d39a11e 100644 --- a/weave/legacy/ops_domain/user_ops.py +++ b/weave/legacy/weave/ops_domain/user_ops.py @@ -1,11 +1,11 @@ -from weave.legacy import weave_types as types -from weave.legacy.api import op -from weave.legacy.gql_op_plugin import wb_gql_op_plugin -from weave.legacy.language_features.tagging.make_tag_getter_op import ( +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.api import op +from weave.legacy.weave.gql_op_plugin import wb_gql_op_plugin +from weave.legacy.weave.language_features.tagging.make_tag_getter_op import ( make_tag_getter_op, ) -from weave.legacy.ops_domain import wb_domain_types as wdt -from weave.legacy.ops_domain.wandb_domain_gql import ( +from weave.legacy.weave.ops_domain import wb_domain_types as wdt +from weave.legacy.weave.ops_domain.wandb_domain_gql import ( gql_connection_op, gql_direct_edge_op, gql_prop_op, diff --git a/weave/legacy/ops_domain/wandb_domain_gql.py b/weave/legacy/weave/ops_domain/wandb_domain_gql.py similarity index 96% rename from weave/legacy/ops_domain/wandb_domain_gql.py rename to weave/legacy/weave/ops_domain/wandb_domain_gql.py index 554e52f0651..a05a40ece28 100644 --- a/weave/legacy/ops_domain/wandb_domain_gql.py +++ b/weave/legacy/weave/ops_domain/wandb_domain_gql.py @@ -4,14 +4,14 @@ import pyarrow as pa -from weave.legacy import weave_types -from weave.legacy.api import op -from weave.legacy import errors, gql_op_plugin, op_def, partial_object -from weave.legacy.decorator_arrow_op import arrow_op -from weave.legacy.gql_op_plugin import wb_gql_op_plugin -from weave.legacy.input_provider import InputProvider -from weave.legacy.ops_arrow import ArrowWeaveList, ArrowWeaveListType -from weave.legacy.ops_domain import wb_domain_types +from weave.legacy.weave import weave_types +from weave.legacy.weave.api import op +from weave.legacy.weave import errors, gql_op_plugin, op_def, partial_object +from weave.legacy.weave.decorator_arrow_op import arrow_op +from weave.legacy.weave.gql_op_plugin import wb_gql_op_plugin +from weave.legacy.weave.input_provider import InputProvider +from weave.legacy.weave.ops_arrow import ArrowWeaveList, ArrowWeaveListType +from weave.legacy.weave.ops_domain import wb_domain_types """ This file contains utilities for constructing GQL ops (used by all the ops in diff --git a/weave/legacy/ops_domain/wb_domain_types.py b/weave/legacy/weave/ops_domain/wb_domain_types.py similarity index 97% rename from weave/legacy/ops_domain/wb_domain_types.py rename to weave/legacy/weave/ops_domain/wb_domain_types.py index b1bf98bef0d..e99912883b6 100644 --- a/weave/legacy/ops_domain/wb_domain_types.py +++ b/weave/legacy/weave/ops_domain/wb_domain_types.py @@ -1,8 +1,8 @@ import typing -from weave.legacy import weave_types as types -from weave.legacy.decorator_type import type as weave_type -from weave.legacy.partial_object import ( +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.decorator_type import type as weave_type +from weave.legacy.weave.partial_object import ( PartialObject, PartialObjectTypeGeneratorType, ) diff --git a/weave/legacy/ops_domain/wb_util.py b/weave/legacy/weave/ops_domain/wb_util.py similarity index 87% rename from weave/legacy/ops_domain/wb_util.py rename to weave/legacy/weave/ops_domain/wb_util.py index 8ba2582a2ca..9265f24777b 100644 --- a/weave/legacy/ops_domain/wb_util.py +++ b/weave/legacy/weave/ops_domain/wb_util.py @@ -2,15 +2,15 @@ from dataclasses import dataclass from urllib import parse -from weave.legacy import weave_types as types -from weave.legacy import artifact_fs, decorator_type -from weave.legacy.artifact_wandb import ( +from weave.legacy.weave import weave_types as types +from weave.legacy.weave import artifact_fs, decorator_type +from weave.legacy.weave.artifact_wandb import ( WandbArtifact, WeaveWBLoggedArtifactURI, ) -from weave.legacy.ops_domain.run_history import history_op_common -from weave.legacy.wandb_interface import wandb_stream_table -from weave.legacy.runfiles_wandb import WandbRunFiles, WeaveWBRunFilesURI +from weave.legacy.weave.ops_domain.run_history import history_op_common +from weave.legacy.weave.wandb_interface import wandb_stream_table +from weave.legacy.weave.runfiles_wandb import WandbRunFiles, WeaveWBRunFilesURI @dataclass @@ -96,7 +96,7 @@ def _process_run_dict_item(val, run_path: typing.Optional[RunPath] = None): return _filesystem_artifact_file_from_artifact_path(val["artifact_path"]) if val["_type"] == "image-file" and run_path is not None: - from weave.legacy.ops_domain import ImageArtifactFileRef + from weave.legacy.weave.ops_domain import ImageArtifactFileRef fs_artifact_file = _filesystem_runfiles_from_run_path(run_path, val["path"]) return ImageArtifactFileRef( @@ -110,7 +110,7 @@ def _process_run_dict_item(val, run_path: typing.Optional[RunPath] = None): # masks=val.get("masks", {}), ) if val["_type"] == "audio-file" and run_path is not None: - from weave.legacy.ops_domain import AudioArtifactFileRef + from weave.legacy.weave.ops_domain import AudioArtifactFileRef fs_artifact_file = _filesystem_runfiles_from_run_path(run_path, val["path"]) return AudioArtifactFileRef( # type: ignore @@ -119,7 +119,7 @@ def _process_run_dict_item(val, run_path: typing.Optional[RunPath] = None): sha256=val["sha256"], ) if val["_type"] == "bokeh-file" and run_path is not None: - from weave.legacy.ops_domain import BokehArtifactFileRef + from weave.legacy.weave.ops_domain import BokehArtifactFileRef fs_artifact_file = _filesystem_runfiles_from_run_path(run_path, val["path"]) return BokehArtifactFileRef( # type: ignore @@ -128,7 +128,7 @@ def _process_run_dict_item(val, run_path: typing.Optional[RunPath] = None): sha256=val["sha256"], ) if val["_type"] == "video-file" and run_path is not None: - from weave.legacy.ops_domain import VideoArtifactFileRef + from weave.legacy.weave.ops_domain import VideoArtifactFileRef fs_artifact_file = _filesystem_runfiles_from_run_path(run_path, val["path"]) return VideoArtifactFileRef( # type: ignore @@ -137,7 +137,7 @@ def _process_run_dict_item(val, run_path: typing.Optional[RunPath] = None): sha256=val["sha256"], ) if val["_type"] == "object3D-file" and run_path is not None: - from weave.legacy.ops_domain import Object3DArtifactFileRef + from weave.legacy.weave.ops_domain import Object3DArtifactFileRef fs_artifact_file = _filesystem_runfiles_from_run_path(run_path, val["path"]) return Object3DArtifactFileRef( # type: ignore @@ -146,7 +146,7 @@ def _process_run_dict_item(val, run_path: typing.Optional[RunPath] = None): sha256=val["sha256"], ) if val["_type"] == "molecule-file" and run_path is not None: - from weave.legacy.ops_domain import MoleculeArtifactFileRef + from weave.legacy.weave.ops_domain import MoleculeArtifactFileRef fs_artifact_file = _filesystem_runfiles_from_run_path(run_path, val["path"]) return MoleculeArtifactFileRef( # type: ignore @@ -155,7 +155,7 @@ def _process_run_dict_item(val, run_path: typing.Optional[RunPath] = None): sha256=val["sha256"], ) if val["_type"] == "html-file" and run_path is not None: - from weave.legacy.ops_domain import HtmlArtifactFileRef + from weave.legacy.weave.ops_domain import HtmlArtifactFileRef fs_artifact_file = _filesystem_runfiles_from_run_path(run_path, val["path"]) return HtmlArtifactFileRef( # type: ignore @@ -165,7 +165,7 @@ def _process_run_dict_item(val, run_path: typing.Optional[RunPath] = None): ) if val["_type"] == "images/separated" and run_path is not None: - from weave.legacy.ops_domain import ImageArtifactFileRef + from weave.legacy.weave.ops_domain import ImageArtifactFileRef image_list: list[ImageArtifactFileRef] = [] @@ -184,7 +184,7 @@ def _process_run_dict_item(val, run_path: typing.Optional[RunPath] = None): return image_list if val["_type"] == "wb_trace_tree": - from weave.legacy.ops_domain.trace_tree import WBTraceTree + from weave.legacy.weave.ops_domain.trace_tree import WBTraceTree return WBTraceTree( root_span_dumps=val.get("root_span_dumps"), # type: ignore diff --git a/weave/legacy/ops_domain/wbgqlquery_op.py b/weave/legacy/weave/ops_domain/wbgqlquery_op.py similarity index 87% rename from weave/legacy/ops_domain/wbgqlquery_op.py rename to weave/legacy/weave/ops_domain/wbgqlquery_op.py index 4841ee249e3..74c50e2e61a 100644 --- a/weave/legacy/ops_domain/wbgqlquery_op.py +++ b/weave/legacy/weave/ops_domain/wbgqlquery_op.py @@ -1,13 +1,13 @@ import logging import typing -from weave.legacy import errors -from weave.legacy import weave_types as types -from weave.legacy.api import op -from weave.legacy import engine_trace, environment, mappers_gql, partial_object -from weave.legacy.language_features.tagging import tagged_value_type -from weave.legacy.ops_domain import wb_domain_types as wdt -from weave.legacy.wandb_client_api import wandb_gql_query +from weave.legacy.weave import errors +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.api import op +from weave.legacy.weave import engine_trace, environment, mappers_gql, partial_object +from weave.legacy.weave.language_features.tagging import tagged_value_type +from weave.legacy.weave.ops_domain import wb_domain_types as wdt +from weave.legacy.weave.wandb_client_api import wandb_gql_query def _wbgqlquery_output_type(input_types: dict[str, types.Type]) -> types.Type: diff --git a/weave/legacy/ops_domain/wbmedia.py b/weave/legacy/weave/ops_domain/wbmedia.py similarity index 97% rename from weave/legacy/ops_domain/wbmedia.py rename to weave/legacy/weave/ops_domain/wbmedia.py index 32e3fe4f937..4256c0872d2 100644 --- a/weave/legacy/ops_domain/wbmedia.py +++ b/weave/legacy/weave/ops_domain/wbmedia.py @@ -4,11 +4,11 @@ import json import typing -from weave.legacy import api as weave +from weave.legacy.weave import api as weave from weave import types -from weave.legacy import errors, engine_trace, artifact_fs, file_base -from weave.legacy.language_features.tagging.tag_store import isolated_tagging_context -from weave.legacy.ops_primitives import html, markdown +from weave.legacy.weave import errors, engine_trace, artifact_fs, file_base +from weave.legacy.weave.language_features.tagging.tag_store import isolated_tagging_context +from weave.legacy.weave.ops_primitives import html, markdown class LegacyImageArtifactFileRef: @@ -283,7 +283,7 @@ class LegacyTableNDArrayType(types.Type): # code can work with it. @weave.op() def html_file(html: html.Html) -> HtmlArtifactFileRef: - from weave.legacy import storage + from weave.legacy.weave import storage # This is a ref to the html object with isolated_tagging_context(): @@ -305,7 +305,7 @@ def html_file(html: html.Html) -> HtmlArtifactFileRef: ) ) def markdown_file(md: markdown.Markdown): - from weave.legacy import storage + from weave.legacy.weave import storage with isolated_tagging_context(): ref = storage.save(md) diff --git a/weave/legacy/ops_primitives/__init__.py b/weave/legacy/weave/ops_primitives/__init__.py similarity index 92% rename from weave/legacy/ops_primitives/__init__.py rename to weave/legacy/weave/ops_primitives/__init__.py index be21b91cb3a..41990e9c7e6 100644 --- a/weave/legacy/ops_primitives/__init__.py +++ b/weave/legacy/weave/ops_primitives/__init__.py @@ -28,7 +28,7 @@ from .html import * from .markdown import * from .op_def import * -from weave.legacy.language_features.tagging.tagging_ops import * +from weave.legacy.weave.language_features.tagging.tagging_ops import * from .list_tag_getters import * from .geom import * from .server import * diff --git a/weave/legacy/ops_primitives/_dict_utils.py b/weave/legacy/weave/ops_primitives/_dict_utils.py similarity index 97% rename from weave/legacy/ops_primitives/_dict_utils.py rename to weave/legacy/weave/ops_primitives/_dict_utils.py index 066c47b8616..780257c0638 100644 --- a/weave/legacy/ops_primitives/_dict_utils.py +++ b/weave/legacy/weave/ops_primitives/_dict_utils.py @@ -1,9 +1,9 @@ import typing -from weave.legacy import errors -from weave.legacy import weave_types as types -from weave.legacy import box -from weave.legacy.language_features.tagging import tag_store, tagged_value_type +from weave.legacy.weave import errors +from weave.legacy.weave import weave_types as types +from weave.legacy.weave import box +from weave.legacy.weave.language_features.tagging import tag_store, tagged_value_type def typeddict_pick_output_type(input_types): diff --git a/weave/legacy/ops_primitives/any.py b/weave/legacy/weave/ops_primitives/any.py similarity index 61% rename from weave/legacy/ops_primitives/any.py rename to weave/legacy/weave/ops_primitives/any.py index f1895ae6c2b..eb10ed9b0c1 100644 --- a/weave/legacy/ops_primitives/any.py +++ b/weave/legacy/weave/ops_primitives/any.py @@ -1,5 +1,5 @@ -from weave.legacy import weave_types as types -from weave.legacy.decorator_op import op +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.decorator_op import op @op( diff --git a/weave/legacy/ops_primitives/artifacts.py b/weave/legacy/weave/ops_primitives/artifacts.py similarity index 96% rename from weave/legacy/ops_primitives/artifacts.py rename to weave/legacy/weave/ops_primitives/artifacts.py index de1e0e0ffa9..9edd8c6e223 100644 --- a/weave/legacy/ops_primitives/artifacts.py +++ b/weave/legacy/weave/ops_primitives/artifacts.py @@ -3,10 +3,10 @@ import pathlib import typing -from weave.legacy.api import op +from weave.legacy.weave.api import op from weave import types -from weave.legacy import artifact_fs, ref_base -from weave.legacy.artifact_local import WORKING_DIR_PREFIX, LocalArtifact +from weave.legacy.weave import artifact_fs, ref_base +from weave.legacy.weave.artifact_local import WORKING_DIR_PREFIX, LocalArtifact @op(name="FilesystemArtifact-fileRefineType", hidden=True) diff --git a/weave/legacy/ops_primitives/boolean.py b/weave/legacy/weave/ops_primitives/boolean.py similarity index 92% rename from weave/legacy/ops_primitives/boolean.py rename to weave/legacy/weave/ops_primitives/boolean.py index 29c6d1ab283..b3f007a22fd 100644 --- a/weave/legacy/ops_primitives/boolean.py +++ b/weave/legacy/weave/ops_primitives/boolean.py @@ -1,9 +1,9 @@ import typing -from weave.legacy import weave_types as types -from weave.legacy.api import op, weave_class -from weave.legacy import dispatch -from weave.legacy.ops_primitives.dict import dict_ +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.api import op, weave_class +from weave.legacy.weave import dispatch +from weave.legacy.weave.ops_primitives.dict import dict_ @weave_class(weave_type=types.Boolean) diff --git a/weave/legacy/ops_primitives/csv_.py b/weave/legacy/weave/ops_primitives/csv_.py similarity index 97% rename from weave/legacy/ops_primitives/csv_.py rename to weave/legacy/weave/ops_primitives/csv_.py index b5f4fe460fb..90a7da01bd2 100644 --- a/weave/legacy/ops_primitives/csv_.py +++ b/weave/legacy/weave/ops_primitives/csv_.py @@ -3,8 +3,8 @@ import pyarrow as pa import pyarrow.csv as pa_csv -from weave.legacy import api as weave -from weave.legacy import file_base +from weave.legacy.weave import api as weave +from weave.legacy.weave import file_base def sniff_dialect(path: str) -> type[csv.Dialect]: diff --git a/weave/legacy/ops_primitives/date.py b/weave/legacy/weave/ops_primitives/date.py similarity index 98% rename from weave/legacy/ops_primitives/date.py rename to weave/legacy/weave/ops_primitives/date.py index 08a5083bfa8..bf08300d3c0 100644 --- a/weave/legacy/ops_primitives/date.py +++ b/weave/legacy/weave/ops_primitives/date.py @@ -4,8 +4,8 @@ import dateutil import dateutil.parser -from weave.legacy import weave_types as types -from weave.legacy.api import op, type +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.api import op, type @op( diff --git a/weave/legacy/ops_primitives/dict.py b/weave/legacy/weave/ops_primitives/dict.py similarity index 92% rename from weave/legacy/ops_primitives/dict.py rename to weave/legacy/weave/ops_primitives/dict.py index cc6e2a25233..a96a9486530 100644 --- a/weave/legacy/ops_primitives/dict.py +++ b/weave/legacy/weave/ops_primitives/dict.py @@ -1,12 +1,12 @@ import typing -from weave.legacy import weave_types as types -from weave.legacy._dict_utils import typeddict_pick_output_type -from weave.legacy.decorator_class import weave_class -from weave.legacy.decorator_op import op -from weave.legacy.language_features.tagging import tagged_value_type -from weave.legacy.op_args import OpVarArgs -from weave.legacy.ops_primitives._dict_utils import ( +from weave.legacy.weave import weave_types as types +from weave.legacy.weave._dict_utils import typeddict_pick_output_type +from weave.legacy.weave.decorator_class import weave_class +from weave.legacy.weave.decorator_op import op +from weave.legacy.weave.language_features.tagging import tagged_value_type +from weave.legacy.weave.op_args import OpVarArgs +from weave.legacy.weave.ops_primitives._dict_utils import ( tag_aware_dict_val_for_escaped_key, ) diff --git a/weave/legacy/ops_primitives/file.py b/weave/legacy/weave/ops_primitives/file.py similarity index 90% rename from weave/legacy/ops_primitives/file.py rename to weave/legacy/weave/ops_primitives/file.py index 75d37ea08db..476c953927b 100644 --- a/weave/legacy/ops_primitives/file.py +++ b/weave/legacy/weave/ops_primitives/file.py @@ -1,13 +1,13 @@ import json import typing -from weave.legacy import environment as weave_env -from weave.legacy import errors -from weave.legacy import weave_types as types -from weave.legacy.api import op -from weave.legacy import file_base, wandb_file_manager -from weave.legacy.artifact_fs import FilesystemArtifactDir, FilesystemArtifactFile -from weave.legacy.artifact_wandb import WandbArtifact, WandbArtifactManifest +from weave.legacy.weave import environment as weave_env +from weave.legacy.weave import errors +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.api import op +from weave.legacy.weave import file_base, wandb_file_manager +from weave.legacy.weave.artifact_fs import FilesystemArtifactDir, FilesystemArtifactFile +from weave.legacy.weave.artifact_wandb import WandbArtifact, WandbArtifactManifest @op(name="dir-pathReturnType", hidden=True) diff --git a/weave/legacy/ops_primitives/file_artifact.py b/weave/legacy/weave/ops_primitives/file_artifact.py similarity index 87% rename from weave/legacy/ops_primitives/file_artifact.py rename to weave/legacy/weave/ops_primitives/file_artifact.py index 203e2486241..299c4e87be5 100644 --- a/weave/legacy/ops_primitives/file_artifact.py +++ b/weave/legacy/weave/ops_primitives/file_artifact.py @@ -1,7 +1,7 @@ import typing -from weave.legacy.api import op -from weave.legacy import artifact_fs +from weave.legacy.weave.api import op +from weave.legacy.weave import artifact_fs @op(name="FilesystemArtifactFile-directUrl") diff --git a/weave/legacy/ops_primitives/file_local.py b/weave/legacy/weave/ops_primitives/file_local.py similarity index 83% rename from weave/legacy/ops_primitives/file_local.py rename to weave/legacy/weave/ops_primitives/file_local.py index 022bf141e19..dbf21b4c1a6 100644 --- a/weave/legacy/ops_primitives/file_local.py +++ b/weave/legacy/weave/ops_primitives/file_local.py @@ -1,10 +1,10 @@ import os import typing -from weave.legacy import environment -from weave.legacy import weave_types as types -from weave.legacy.api import op -from weave.legacy import file_local +from weave.legacy.weave import environment +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.api import op +from weave.legacy.weave import file_local @op(name="LocalFile-directUrl") diff --git a/weave/legacy/ops_primitives/geom.py b/weave/legacy/weave/ops_primitives/geom.py similarity index 93% rename from weave/legacy/ops_primitives/geom.py rename to weave/legacy/weave/ops_primitives/geom.py index e0931c1bd39..32264b7b9d5 100644 --- a/weave/legacy/ops_primitives/geom.py +++ b/weave/legacy/weave/ops_primitives/geom.py @@ -1,6 +1,6 @@ from PIL import Image -from weave.legacy import api as weave +from weave.legacy.weave import api as weave @weave.type() diff --git a/weave/legacy/ops_primitives/html.py b/weave/legacy/weave/ops_primitives/html.py similarity index 82% rename from weave/legacy/ops_primitives/html.py rename to weave/legacy/weave/ops_primitives/html.py index 2103ff69428..72d6d25a45d 100644 --- a/weave/legacy/ops_primitives/html.py +++ b/weave/legacy/weave/ops_primitives/html.py @@ -1,7 +1,7 @@ import dataclasses -from weave.legacy import api as weave -from weave.legacy import weave_types as types +from weave.legacy.weave import api as weave +from weave.legacy.weave import weave_types as types class HtmlType(types.Type): diff --git a/weave/legacy/ops_primitives/image.py b/weave/legacy/weave/ops_primitives/image.py similarity index 93% rename from weave/legacy/ops_primitives/image.py rename to weave/legacy/weave/ops_primitives/image.py index f44f14357ad..9432887943b 100644 --- a/weave/legacy/ops_primitives/image.py +++ b/weave/legacy/weave/ops_primitives/image.py @@ -5,8 +5,8 @@ import PIL import PIL.Image -from weave.legacy import api as weave -from weave.legacy import weave_types as types +from weave.legacy.weave import api as weave +from weave.legacy.weave import weave_types as types class ImageType(types.Type): diff --git a/weave/legacy/ops_primitives/json_.py b/weave/legacy/weave/ops_primitives/json_.py similarity index 90% rename from weave/legacy/ops_primitives/json_.py rename to weave/legacy/weave/ops_primitives/json_.py index 5cefb079741..b781daab769 100644 --- a/weave/legacy/ops_primitives/json_.py +++ b/weave/legacy/weave/ops_primitives/json_.py @@ -1,8 +1,8 @@ import json import typing -from weave.legacy import api as weave -from weave.legacy import file_base +from weave.legacy.weave import api as weave +from weave.legacy.weave import file_base def load_jsonl(jsonlfile): diff --git a/weave/legacy/ops_primitives/list_.py b/weave/legacy/weave/ops_primitives/list_.py similarity index 97% rename from weave/legacy/ops_primitives/list_.py rename to weave/legacy/weave/ops_primitives/list_.py index e61826f6003..6d80a26a62f 100644 --- a/weave/legacy/ops_primitives/list_.py +++ b/weave/legacy/weave/ops_primitives/list_.py @@ -6,20 +6,20 @@ import numpy as np import pandas as pd -from weave.legacy import storage -from weave.legacy import weave_types as types -from weave.legacy import box, execute_fast, errors -from weave.legacy._dict_utils import tag_aware_dict_val_for_escaped_key -from weave.legacy.decorator_class import weave_class -from weave.legacy.decorator_op import op -from weave.legacy.graph import Node -from weave.legacy.language_features.tagging import ( +from weave.legacy.weave import storage +from weave.legacy.weave import weave_types as types +from weave.legacy.weave import box, execute_fast, errors +from weave.legacy.weave._dict_utils import tag_aware_dict_val_for_escaped_key +from weave.legacy.weave.decorator_class import weave_class +from weave.legacy.weave.decorator_op import op +from weave.legacy.weave.graph import Node +from weave.legacy.weave.language_features.tagging import ( tag_store, tagged_value_type, tagged_value_type_helpers, ) -from weave.legacy.op_args import OpVarArgs -from weave.legacy.ops_primitives import projection_utils +from weave.legacy.weave.op_args import OpVarArgs +from weave.legacy.weave.ops_primitives import projection_utils def getitem_output_type(input_types, list_type=types.List): @@ -392,8 +392,8 @@ def flatten_return_type(input_types): def _flatten(l): - from weave.legacy.arrow.arrow_tags import pushdown_list_tags - from weave.legacy.ops_arrow import ArrowWeaveList + from weave.legacy.weave.arrow.arrow_tags import pushdown_list_tags + from weave.legacy.weave.ops_arrow import ArrowWeaveList if isinstance(l, list): tags = None diff --git a/weave/legacy/ops_primitives/list_tag_getters.py b/weave/legacy/weave/ops_primitives/list_tag_getters.py similarity index 74% rename from weave/legacy/ops_primitives/list_tag_getters.py rename to weave/legacy/weave/ops_primitives/list_tag_getters.py index 61e57c3ae93..ca21015dc17 100644 --- a/weave/legacy/ops_primitives/list_tag_getters.py +++ b/weave/legacy/weave/ops_primitives/list_tag_getters.py @@ -1,5 +1,5 @@ -from weave.legacy import weave_types as types -from weave.legacy.language_features.tagging import make_tag_getter_op +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.language_features.tagging import make_tag_getter_op group_tag_getter_op = make_tag_getter_op.make_tag_getter_op( "groupKey", types.Any(), op_name="group-groupkey" diff --git a/weave/legacy/ops_primitives/markdown.py b/weave/legacy/weave/ops_primitives/markdown.py similarity index 82% rename from weave/legacy/ops_primitives/markdown.py rename to weave/legacy/weave/ops_primitives/markdown.py index ad3822dfdfc..b82e7e547f9 100644 --- a/weave/legacy/ops_primitives/markdown.py +++ b/weave/legacy/weave/ops_primitives/markdown.py @@ -1,7 +1,7 @@ import dataclasses -from weave.legacy import api as weave -from weave.legacy import weave_types as types +from weave.legacy.weave import api as weave +from weave.legacy.weave import weave_types as types class MarkdownType(types.Type): diff --git a/weave/legacy/ops_primitives/number.py b/weave/legacy/weave/ops_primitives/number.py similarity index 98% rename from weave/legacy/ops_primitives/number.py rename to weave/legacy/weave/ops_primitives/number.py index 2cb4aec1254..c260617c53b 100644 --- a/weave/legacy/ops_primitives/number.py +++ b/weave/legacy/weave/ops_primitives/number.py @@ -4,9 +4,9 @@ import numpy as np -from weave.legacy import weave_types as types -from weave.legacy.api import op, weave_class -from weave.legacy import timestamp as weave_timestamp +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.api import op, weave_class +from weave.legacy.weave import timestamp as weave_timestamp binary_number_op_input_type = { "lhs": types.Number(), diff --git a/weave/legacy/ops_primitives/number_bin.py b/weave/legacy/weave/ops_primitives/number_bin.py similarity index 81% rename from weave/legacy/ops_primitives/number_bin.py rename to weave/legacy/weave/ops_primitives/number_bin.py index c06de46703d..d60e531c268 100644 --- a/weave/legacy/ops_primitives/number_bin.py +++ b/weave/legacy/weave/ops_primitives/number_bin.py @@ -1,12 +1,12 @@ import math -from weave.legacy import weave_types as types -from weave.legacy.api import op, use -from weave.legacy import graph -from weave.legacy.ops_primitives import date -from weave.legacy.ops_primitives.dict import dict_ -from weave.weave_internal import call_fn, define_fn, make_const_node -from weave.legacy.weave_types import Function, NumberBinType +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.api import op, use +from weave.legacy.weave import graph +from weave.legacy.weave.ops_primitives import date +from weave.legacy.weave.ops_primitives.dict import dict_ +from weave.legacy.weave.weave_internal import call_fn, define_fn, make_const_node +from weave.legacy.weave.weave_types import Function, NumberBinType @op( diff --git a/weave/legacy/ops_primitives/obj.py b/weave/legacy/weave/ops_primitives/obj.py similarity index 93% rename from weave/legacy/ops_primitives/obj.py rename to weave/legacy/weave/ops_primitives/obj.py index 615fe8eea3f..35e24284d2c 100644 --- a/weave/legacy/ops_primitives/obj.py +++ b/weave/legacy/weave/ops_primitives/obj.py @@ -1,8 +1,8 @@ import typing -from weave.legacy import codify -from weave.legacy import weave_types as types -from weave.legacy.api import op, weave_class +from weave.legacy.weave import codify +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.api import op, weave_class # This matches the output type logic of the frontend diff --git a/weave/legacy/ops_primitives/op_def.py b/weave/legacy/weave/ops_primitives/op_def.py similarity index 68% rename from weave/legacy/ops_primitives/op_def.py rename to weave/legacy/weave/ops_primitives/op_def.py index 81caf0be0e2..1d7458476fe 100644 --- a/weave/legacy/ops_primitives/op_def.py +++ b/weave/legacy/weave/ops_primitives/op_def.py @@ -1,7 +1,7 @@ -from weave.legacy import api as weave -from weave.legacy import weave_types as types -from weave.legacy.op_def import OpDef -from weave.legacy.op_def_type import OpDefType +from weave.legacy.weave import api as weave +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.op_def import OpDef +from weave.legacy.weave.op_def_type import OpDefType @weave.weave_class(weave_type=OpDefType) diff --git a/weave/legacy/ops_primitives/pandas_.py b/weave/legacy/weave/ops_primitives/pandas_.py similarity index 97% rename from weave/legacy/ops_primitives/pandas_.py rename to weave/legacy/weave/ops_primitives/pandas_.py index 97a9f30c2db..d1e286ed931 100644 --- a/weave/legacy/ops_primitives/pandas_.py +++ b/weave/legacy/weave/ops_primitives/pandas_.py @@ -8,12 +8,12 @@ import pyarrow as pa import pyarrow.parquet as pq -from weave.legacy import errors -from weave.legacy import weave_types as types -from weave.legacy.api import op, weave_class -from weave.legacy import box, file_base, graph, mappers_python -from weave.legacy.language_features.tagging import tag_store, tagged_value_type -from weave.legacy.ops_primitives import list_ +from weave.legacy.weave import errors +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.api import op, weave_class +from weave.legacy.weave import box, file_base, graph, mappers_python +from weave.legacy.weave.language_features.tagging import tag_store, tagged_value_type +from weave.legacy.weave.ops_primitives import list_ # Hack hack hack diff --git a/weave/legacy/ops_primitives/projection_utils.py b/weave/legacy/weave/ops_primitives/projection_utils.py similarity index 98% rename from weave/legacy/ops_primitives/projection_utils.py rename to weave/legacy/weave/ops_primitives/projection_utils.py index 49e96d1e562..127c41d8773 100644 --- a/weave/legacy/ops_primitives/projection_utils.py +++ b/weave/legacy/weave/ops_primitives/projection_utils.py @@ -9,8 +9,8 @@ from sklearn.decomposition import PCA from sklearn.manifold import TSNE -from weave.legacy import errors -from weave.legacy import context_state, environment +from weave.legacy.weave import errors +from weave.legacy.weave import context_state, environment umap_lib = {} diff --git a/weave/legacy/ops_primitives/random_junk.py b/weave/legacy/weave/ops_primitives/random_junk.py similarity index 82% rename from weave/legacy/ops_primitives/random_junk.py rename to weave/legacy/weave/ops_primitives/random_junk.py index e77ffe0a48b..b158c1221ab 100644 --- a/weave/legacy/ops_primitives/random_junk.py +++ b/weave/legacy/weave/ops_primitives/random_junk.py @@ -1,8 +1,8 @@ # Ideas for ops, but not production ready. -from weave.legacy import api as api -from weave.legacy import weave_types as types -from weave.legacy.api import op, weave_class +from weave.legacy.weave import api as api +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.api import op, weave_class op( name="root-compare_versions", diff --git a/weave/legacy/ops_primitives/server.py b/weave/legacy/weave/ops_primitives/server.py similarity index 76% rename from weave/legacy/ops_primitives/server.py rename to weave/legacy/weave/ops_primitives/server.py index ccc93e6762d..536343a5d9f 100644 --- a/weave/legacy/ops_primitives/server.py +++ b/weave/legacy/weave/ops_primitives/server.py @@ -1,4 +1,4 @@ -from weave.legacy.decorator_op import op +from weave.legacy.weave.decorator_op import op from weave.version import VERSION diff --git a/weave/legacy/ops_primitives/set_.py b/weave/legacy/weave/ops_primitives/set_.py similarity index 94% rename from weave/legacy/ops_primitives/set_.py rename to weave/legacy/weave/ops_primitives/set_.py index d64e7e9f845..f73d5627709 100644 --- a/weave/legacy/ops_primitives/set_.py +++ b/weave/legacy/weave/ops_primitives/set_.py @@ -1,5 +1,5 @@ -from weave.legacy import weave_types as types -from weave.legacy.decorators import op +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.decorators import op def get_const_union_vals(type_, of_type): diff --git a/weave/legacy/ops_primitives/sql.py b/weave/legacy/weave/ops_primitives/sql.py similarity index 97% rename from weave/legacy/ops_primitives/sql.py rename to weave/legacy/weave/ops_primitives/sql.py index 7c05a41a213..f4f78567744 100644 --- a/weave/legacy/ops_primitives/sql.py +++ b/weave/legacy/weave/ops_primitives/sql.py @@ -2,11 +2,11 @@ import functools import math -from weave.legacy import weave_types as types -from weave.legacy.api import op, weave_class -from weave.legacy import decorator_type -from weave.legacy.language_features.tagging import tagged_value_type -from weave.legacy.ops_primitives import graph, list_ +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.api import op, weave_class +from weave.legacy.weave import decorator_type +from weave.legacy.weave.language_features.tagging import tagged_value_type +from weave.legacy.weave.ops_primitives import graph, list_ try: import sqlalchemy diff --git a/weave/legacy/ops_primitives/string.py b/weave/legacy/weave/ops_primitives/string.py similarity index 98% rename from weave/legacy/ops_primitives/string.py rename to weave/legacy/weave/ops_primitives/string.py index db86ae6d233..6f0433f801f 100644 --- a/weave/legacy/ops_primitives/string.py +++ b/weave/legacy/weave/ops_primitives/string.py @@ -4,8 +4,8 @@ import numpy as np -from weave.legacy import weave_types as types -from weave.legacy.api import op, weave_class +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.api import op, weave_class @op(name="root-string") diff --git a/weave/legacy/ops_primitives/test_any.py b/weave/legacy/weave/ops_primitives/test_any.py similarity index 73% rename from weave/legacy/ops_primitives/test_any.py rename to weave/legacy/weave/ops_primitives/test_any.py index 4fd7cd14952..95d2c93c2af 100644 --- a/weave/legacy/ops_primitives/test_any.py +++ b/weave/legacy/weave/ops_primitives/test_any.py @@ -2,9 +2,9 @@ import pytest -from weave.legacy import api as weave -from weave.legacy import box -from weave.legacy.ops_primitives import any +from weave.legacy.weave import api as weave +from weave.legacy.weave import box +from weave.legacy.weave.ops_primitives import any cases: list[typing.Tuple[typing.Any, bool]] = [ (1, False), diff --git a/weave/legacy/ops_primitives/test_dict.py b/weave/legacy/weave/ops_primitives/test_dict.py similarity index 80% rename from weave/legacy/ops_primitives/test_dict.py rename to weave/legacy/weave/ops_primitives/test_dict.py index 33508af74e2..04a85b7509b 100644 --- a/weave/legacy/ops_primitives/test_dict.py +++ b/weave/legacy/weave/ops_primitives/test_dict.py @@ -1,6 +1,6 @@ import weave -from weave import weave_internal -from weave.legacy.language_features.tagging import tagged_value_type +from weave.legacy.weave import weave_internal +from weave.legacy.weave.language_features.tagging import tagged_value_type def test_keys_type(): @@ -25,7 +25,7 @@ def test_pick(): ), "a", ) - assert weave.use(weave.legacy.ops.TypedDict.pick(obj, key)) == "x" + assert weave.use(weave.legacy.weave.ops.TypedDict.pick(obj, key)) == "x" def test_pick_none_key(): diff --git a/weave/legacy/ops_primitives/test_file.py b/weave/legacy/weave/ops_primitives/test_file.py similarity index 54% rename from weave/legacy/ops_primitives/test_file.py rename to weave/legacy/weave/ops_primitives/test_file.py index 2715f67a579..247beb744f5 100644 --- a/weave/legacy/ops_primitives/test_file.py +++ b/weave/legacy/weave/ops_primitives/test_file.py @@ -1,8 +1,8 @@ import weave -from weave.legacy.ops_primitives import file_local +from weave.legacy.weave.ops_primitives import file_local def test_file_browsing(): - test_dir = weave.legacy.ops.local_path("./testdata/").path("test_dir") + test_dir = weave.legacy.weave.ops.local_path("./testdata/").path("test_dir") assert test_dir.type == file_local.LocalDirType() assert weave.use(test_dir.path("b.txt").contents()) == "howdy\n" diff --git a/weave/legacy/ops_primitives/test_image.py b/weave/legacy/weave/ops_primitives/test_image.py similarity index 89% rename from weave/legacy/ops_primitives/test_image.py rename to weave/legacy/weave/ops_primitives/test_image.py index 12c0e7868fe..66e17caac85 100644 --- a/weave/legacy/ops_primitives/test_image.py +++ b/weave/legacy/weave/ops_primitives/test_image.py @@ -2,8 +2,8 @@ import pytest from PIL import Image -from weave.legacy import storage -from weave.legacy.ops_primitives import api as weave +from weave.legacy.weave import storage +from weave.legacy.weave.ops_primitives import api as weave def test_image(): diff --git a/weave/legacy/ops_primitives/test_list.py b/weave/legacy/weave/ops_primitives/test_list.py similarity index 95% rename from weave/legacy/ops_primitives/test_list.py rename to weave/legacy/weave/ops_primitives/test_list.py index 2856054b5ab..f2bf31f7cd4 100644 --- a/weave/legacy/ops_primitives/test_list.py +++ b/weave/legacy/weave/ops_primitives/test_list.py @@ -1,16 +1,16 @@ import pytest -from weave.legacy import api as weave -from weave import weave_internal -from weave.legacy import weave_types as types -from weave.legacy import box -from weave.legacy.language_features.tagging import ( +from weave.legacy.weave import api as weave +from weave.legacy.weave import weave_internal +from weave.legacy.weave import weave_types as types +from weave.legacy.weave import box +from weave.legacy.weave.language_features.tagging import ( make_tag_getter_op, tag_store, tagged_value_type, ) -from weave.legacy.ops_primitives import dict, errors, list_, number, runs -from weave.tests import geom, weavejs_ops +from weave.legacy.weave.ops_primitives import dict, errors, list_, number, runs +from weave.legacy.tests.util import geom, weavejs_ops def test_unnest(): diff --git a/weave/legacy/weave/ops_primitives/test_list_range.py b/weave/legacy/weave/ops_primitives/test_list_range.py new file mode 100644 index 00000000000..d821293f33d --- /dev/null +++ b/weave/legacy/weave/ops_primitives/test_list_range.py @@ -0,0 +1,7 @@ +import pytest + +import weave + + +def test_range(): + assert weave.use(weave.legacy.weave.ops.range(0, 3, 1)).to_pylist_tagged() == [0, 1, 2] diff --git a/weave/legacy/ops_primitives/test_pandas.py b/weave/legacy/weave/ops_primitives/test_pandas.py similarity index 85% rename from weave/legacy/ops_primitives/test_pandas.py rename to weave/legacy/weave/ops_primitives/test_pandas.py index 835aeba71f8..f6977800091 100644 --- a/weave/legacy/ops_primitives/test_pandas.py +++ b/weave/legacy/weave/ops_primitives/test_pandas.py @@ -1,8 +1,8 @@ import pandas as pd -from weave.legacy import api as weave -from weave.legacy import weave_types as types -from weave.legacy.ops_primitives import pandas_ as op_pandas +from weave.legacy.weave import api as weave +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.ops_primitives import pandas_ as op_pandas def test_save_dataframe(): diff --git a/weave/legacy/ops_primitives/test_set.py b/weave/legacy/weave/ops_primitives/test_set.py similarity index 89% rename from weave/legacy/ops_primitives/test_set.py rename to weave/legacy/weave/ops_primitives/test_set.py index 2fb0ffebbf4..96b2048a7df 100644 --- a/weave/legacy/ops_primitives/test_set.py +++ b/weave/legacy/weave/ops_primitives/test_set.py @@ -1,5 +1,5 @@ import weave -from weave.legacy import ops_primitives, val_const +from weave.legacy.weave import ops_primitives, val_const def test_difference_type(): diff --git a/weave/legacy/ops_primitives/test_type.py b/weave/legacy/weave/ops_primitives/test_type.py similarity index 79% rename from weave/legacy/ops_primitives/test_type.py rename to weave/legacy/weave/ops_primitives/test_type.py index a2dff5d9c96..4240de94abf 100644 --- a/weave/legacy/ops_primitives/test_type.py +++ b/weave/legacy/weave/ops_primitives/test_type.py @@ -4,11 +4,11 @@ def test_cast_basic(): - valid_cast = weave.legacy.ops.cast(1, weave.types.Int()) + valid_cast = weave.legacy.weave.ops.cast(1, weave.types.Int()) assert valid_cast.type == weave.types.Int() assert weave.use(valid_cast) == 1 - invalid_cast = weave.legacy.ops.cast(1, weave.types.String()) + invalid_cast = weave.legacy.weave.ops.cast(1, weave.types.String()) assert invalid_cast.type == weave.types.String() with pytest.raises(weave.errors.WeaveTypeError): weave.use(invalid_cast) diff --git a/weave/legacy/ops_primitives/test_typeddict.py b/weave/legacy/weave/ops_primitives/test_typeddict.py similarity index 96% rename from weave/legacy/ops_primitives/test_typeddict.py rename to weave/legacy/weave/ops_primitives/test_typeddict.py index 94644dae54e..6f57ee0ef8a 100644 --- a/weave/legacy/ops_primitives/test_typeddict.py +++ b/weave/legacy/weave/ops_primitives/test_typeddict.py @@ -1,11 +1,11 @@ import pytest import weave -from weave import weave_internal -from weave.legacy import box -from weave.legacy.arrow.arrow import ArrowWeaveListType -from weave.legacy.language_features.tagging import tag_store, tagged_value_type -from weave.tests.list_arrow_test_helpers import ArrowNode +from weave.legacy.weave import weave_internal +from weave.legacy.weave import box +from weave.legacy.weave.arrow.arrow import ArrowWeaveListType +from weave.legacy.weave.language_features.tagging import tag_store, tagged_value_type +from weave.legacy.tests.util.list_arrow_test_helpers import ArrowNode pick_options = [ # Basic Pick diff --git a/weave/legacy/ops_primitives/timestamp_bin.py b/weave/legacy/weave/ops_primitives/timestamp_bin.py similarity index 86% rename from weave/legacy/ops_primitives/timestamp_bin.py rename to weave/legacy/weave/ops_primitives/timestamp_bin.py index ef153be48da..0263ec39fe6 100644 --- a/weave/legacy/ops_primitives/timestamp_bin.py +++ b/weave/legacy/weave/ops_primitives/timestamp_bin.py @@ -1,10 +1,10 @@ -from weave import weave_internal -from weave.legacy import weave_types as types -from weave.legacy.api import op, use -from weave.legacy import graph -from weave.legacy.ops_primitives.dict import dict_ -from weave.weave_internal import call_fn, define_fn, make_const_node -from weave.legacy.weave_types import Function, TimestampBinType +from weave.legacy.weave import weave_internal +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.api import op, use +from weave.legacy.weave import graph +from weave.legacy.weave.ops_primitives.dict import dict_ +from weave.legacy.weave.weave_internal import call_fn, define_fn, make_const_node +from weave.legacy.weave.weave_types import Function, TimestampBinType NICE_BIN_SIZES_SEC = [ # TODO: will need more steps along here for smooth zooming. diff --git a/weave/legacy/ops_primitives/type.py b/weave/legacy/weave/ops_primitives/type.py similarity index 85% rename from weave/legacy/ops_primitives/type.py rename to weave/legacy/weave/ops_primitives/type.py index 4b39df3143e..a2575507f75 100644 --- a/weave/legacy/ops_primitives/type.py +++ b/weave/legacy/weave/ops_primitives/type.py @@ -1,8 +1,8 @@ import typing -from weave.legacy import errors -from weave.legacy import weave_types as types -from weave.legacy.api import op +from weave.legacy.weave import errors +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.api import op @op( diff --git a/weave/legacy/ops_primitives/weave_api.py b/weave/legacy/weave/ops_primitives/weave_api.py similarity index 99% rename from weave/legacy/ops_primitives/weave_api.py rename to weave/legacy/weave/ops_primitives/weave_api.py index 680e21c5460..6912fca77ef 100644 --- a/weave/legacy/ops_primitives/weave_api.py +++ b/weave/legacy/weave/ops_primitives/weave_api.py @@ -2,13 +2,11 @@ import time import typing -from weave import ( +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.api import mutation, op, weave_class +from weave.legacy.weave import ( storage, weave_internal, -) -from weave.legacy import weave_types as types -from weave.legacy.api import mutation, op, weave_class -from weave.legacy import ( errors, ref_base, registry_mem, @@ -22,7 +20,7 @@ trace_legacy, uris, ) -from weave.legacy.graph import Node +from weave.legacy.weave.graph import Node @weave_class(weave_type=types.RefType) @@ -635,7 +633,7 @@ def append( @op(mutation=True, name="stream_table-log") def stream_table_log(self: graph.Node, val: typing.Any) -> typing.Any: st_obj = weave_internal.use(self) - from weave.legacy.monitoring import StreamTable + from weave.legacy.weave.monitoring import StreamTable if not isinstance(st_obj, StreamTable): raise errors.WeaveInternalError( diff --git a/weave/legacy/panel.py b/weave/legacy/weave/panel.py similarity index 95% rename from weave/legacy/panel.py rename to weave/legacy/weave/panel.py index 7ec6daab4e0..4938235861e 100644 --- a/weave/legacy/panel.py +++ b/weave/legacy/weave/panel.py @@ -3,10 +3,10 @@ import typing from tarfile import DEFAULT_FORMAT -from weave.legacy import api as weave -from weave import weave_internal -from weave.legacy import weave_types as types -from weave.legacy import graph, panel_util, errors, storage +from weave.legacy.weave import api as weave +from weave.legacy.weave import weave_internal +from weave.legacy.weave import weave_types as types +from weave.legacy.weave import graph, panel_util, errors, storage def run_variable_lambdas( @@ -157,6 +157,6 @@ def to_json(self): } def _ipython_display_(self): - from weave.legacy import show + from weave.legacy.weave import show show.show(self) diff --git a/weave/legacy/panel_util.py b/weave/legacy/weave/panel_util.py similarity index 80% rename from weave/legacy/panel_util.py rename to weave/legacy/weave/panel_util.py index 5c86926c92e..87c28b25f1d 100644 --- a/weave/legacy/panel_util.py +++ b/weave/legacy/weave/panel_util.py @@ -1,12 +1,12 @@ import typing -from weave.legacy import weave_types -from weave.legacy import storage, graph, panel +from weave.legacy.weave import weave_types +from weave.legacy.weave import storage, graph, panel def make_node(v: typing.Any) -> graph.Node: """Business logic for how values passed to panels are converted to json.""" - from weave.legacy import ops + from weave.legacy.weave import ops if isinstance(v, graph.Node): return v diff --git a/weave/legacy/weave/panels/__init__.py b/weave/legacy/weave/panels/__init__.py new file mode 100644 index 00000000000..1ba950300b7 --- /dev/null +++ b/weave/legacy/weave/panels/__init__.py @@ -0,0 +1,70 @@ +from weave.legacy.weave import context_state as _context_state + +_loading_builtins_token = _context_state.set_loading_built_ins() + +from weave.legacy.weave.panels.panel_auto import * + +# basic +from weave.legacy.weave.panels.panel_basic import * + +# top level board +from weave.legacy.weave.panels.panel_board import Board, BoardPanel, BoardPanelLayout +from weave.legacy.weave.panels.panel_card import Card, CardTab +from weave.legacy.weave.panels.panel_color import Color +from weave.legacy.weave.panels.panel_daterange import DateRange + +# domain +from weave.legacy.weave.panels.panel_domain import * +from weave.legacy.weave.panels.panel_dropdown import Dropdown, DropdownConfig +from weave.legacy.weave.panels.panel_each import Each +from weave.legacy.weave.panels.panel_each_column import EachColumn + +# special +from weave.legacy.weave.panels.panel_expression import * +from weave.legacy.weave.panels.panel_facet import Facet +from weave.legacy.weave.panels.panel_facet_tabs import FacetTabs +from weave.legacy.weave.panels.panel_filter_editor import FilterEditor +from weave.legacy.weave.panels.panel_function_editor import ( + FunctionEditor, + FunctionEditorConfig, +) +from weave.legacy.weave.panels.panel_group import ( + Group, + GroupLayoutFlow, + GroupPanel, + GroupPanelLayout, +) +from weave.legacy.weave.panels.panel_grouping_editor import GroupingEditor + +# Incomplete +from weave.legacy.weave.panels.panel_histogram import * +from weave.legacy.weave.panels.panel_html import PanelHtml + +# layout +from weave.legacy.weave.panels.panel_labeled_item import LabeledItem + +# legacy +from weave.legacy.weave.panels.panel_legacy import * +from weave.legacy.weave.panels.panel_markdown import PanelMarkdown + +# Non-standard editor (todo: update) +from weave.legacy.weave.panels.panel_object_picker import ObjectPicker, ObjectPickerConfig +from weave.legacy.weave.panels.panel_plot import Plot, PlotConstants, Series + +# sidebar specific +from weave.legacy.weave.panels.panel_query import Query, QueryCondition, QueryConfig +from weave.legacy.weave.panels.panel_sections import Sections +from weave.legacy.weave.panels.panel_select import SelectEditor, SelectEditorConfig + +# editors +from weave.legacy.weave.panels.panel_slider import Slider, SliderConfig +from weave.legacy.weave.panels.panel_string import PanelString +from weave.legacy.weave.panels.panel_string_editor import StringEditor +from weave.legacy.weave.panels.panel_table import ColumnDef, Table, TableColumn +from weave.legacy.weave.panels.panel_trace import Trace + +# navigation +from weave.legacy.weave.panels.panel_weavelink import WeaveLink +from weave.legacy.weave.panel import Panel + +_context_state.clear_loading_built_ins(_loading_builtins_token) diff --git a/weave/legacy/panels/bank.py b/weave/legacy/weave/panels/bank.py similarity index 93% rename from weave/legacy/panels/bank.py rename to weave/legacy/weave/panels/bank.py index b5762d9fe38..0568ae389c2 100644 --- a/weave/legacy/panels/bank.py +++ b/weave/legacy/weave/panels/bank.py @@ -1,6 +1,6 @@ # TODO: move all panelbank stuff here -from weave.legacy.panels import panel_group +from weave.legacy.weave.panels import panel_group def default_panel_bank_flow_section_config(): diff --git a/weave/legacy/panels/panel_auto.py b/weave/legacy/weave/panels/panel_auto.py similarity index 77% rename from weave/legacy/panels/panel_auto.py rename to weave/legacy/weave/panels/panel_auto.py index a50fe9f4170..f97027755b9 100644 --- a/weave/legacy/panels/panel_auto.py +++ b/weave/legacy/weave/panels/panel_auto.py @@ -1,5 +1,5 @@ import weave -from weave.legacy import panel +from weave.legacy.weave import panel # Currently Auto is not a real panel, the system handles it. diff --git a/weave/legacy/panels/panel_basic.py b/weave/legacy/weave/panels/panel_basic.py similarity index 84% rename from weave/legacy/panels/panel_basic.py rename to weave/legacy/weave/panels/panel_basic.py index ffabed4b613..8ae26009cf2 100644 --- a/weave/legacy/panels/panel_basic.py +++ b/weave/legacy/weave/panels/panel_basic.py @@ -1,5 +1,5 @@ import weave -from weave.legacy import panel +from weave.legacy.weave import panel @weave.type() diff --git a/weave/legacy/panels/panel_board.py b/weave/legacy/weave/panels/panel_board.py similarity index 92% rename from weave/legacy/panels/panel_board.py rename to weave/legacy/weave/panels/panel_board.py index 6f3ac7e94dd..ab14570b1ef 100644 --- a/weave/legacy/panels/panel_board.py +++ b/weave/legacy/weave/panels/panel_board.py @@ -2,9 +2,9 @@ import typing import weave -from weave import weave_internal -from weave.legacy import panel -from weave.legacy.panels import panel_group +from weave.legacy.weave import weave_internal +from weave.legacy.weave import panel +from weave.legacy.weave.panels import panel_group @dataclasses.dataclass @@ -51,7 +51,7 @@ def varbar(editable=True, items=None) -> panel_group.Group: def main() -> panel_group.Group: - return weave.legacy.panels.Group( + return weave.legacy.weave.panels.Group( layoutMode="grid", showExpressions=True, enableAddPanel=True, @@ -65,11 +65,11 @@ def Board( ): showExpressions = True if editable else "titleBar" vb = vars - if not isinstance(vb, weave.legacy.panels.Group): + if not isinstance(vb, weave.legacy.weave.panels.Group): vb = varbar(editable=editable, items=vars) main = panels - if not isinstance(panels, weave.legacy.panels.Group): + if not isinstance(panels, weave.legacy.weave.panels.Group): main_items = {} main_panel_layouts: list[panel_group.LayedOutPanel] = [] for i, p in enumerate(panels): diff --git a/weave/legacy/panels/panel_card.py b/weave/legacy/weave/panels/panel_card.py similarity index 93% rename from weave/legacy/panels/panel_card.py rename to weave/legacy/weave/panels/panel_card.py index 8979583edf4..0eacbcba710 100644 --- a/weave/legacy/panels/panel_card.py +++ b/weave/legacy/weave/panels/panel_card.py @@ -2,7 +2,7 @@ import typing import weave -from weave.legacy import graph, panel, panel_util +from weave.legacy.weave import graph, panel, panel_util CardContentType = typing.TypeVar("CardContentType") diff --git a/weave/legacy/panels/panel_color.py b/weave/legacy/weave/panels/panel_color.py similarity index 81% rename from weave/legacy/panels/panel_color.py rename to weave/legacy/weave/panels/panel_color.py index eb44072878e..f984a7fbba4 100644 --- a/weave/legacy/panels/panel_color.py +++ b/weave/legacy/weave/panels/panel_color.py @@ -2,7 +2,7 @@ import typing import weave -from weave.legacy import graph, panel, panel_util +from weave.legacy.weave import graph, panel, panel_util @weave.type() diff --git a/weave/legacy/panels/panel_daterange.py b/weave/legacy/weave/panels/panel_daterange.py similarity index 93% rename from weave/legacy/panels/panel_daterange.py rename to weave/legacy/weave/panels/panel_daterange.py index 262c64e5410..f06df0b3c75 100644 --- a/weave/legacy/panels/panel_daterange.py +++ b/weave/legacy/weave/panels/panel_daterange.py @@ -2,7 +2,7 @@ import typing import weave -from weave.legacy import graph, panel, panel_util +from weave.legacy.weave import graph, panel, panel_util @weave.type() diff --git a/weave/legacy/panels/panel_domain.py b/weave/legacy/weave/panels/panel_domain.py similarity index 90% rename from weave/legacy/panels/panel_domain.py rename to weave/legacy/weave/panels/panel_domain.py index f3d0ad9307b..535edd09dab 100644 --- a/weave/legacy/panels/panel_domain.py +++ b/weave/legacy/weave/panels/panel_domain.py @@ -1,5 +1,5 @@ import weave -from weave.legacy import panel +from weave.legacy.weave import panel @weave.type(__override_name="wb_trace_tree-traceViewer") # type: ignore diff --git a/weave/legacy/panels/panel_dropdown.py b/weave/legacy/weave/panels/panel_dropdown.py similarity index 88% rename from weave/legacy/panels/panel_dropdown.py rename to weave/legacy/weave/panels/panel_dropdown.py index e39d4e92a87..6eda21e6563 100644 --- a/weave/legacy/panels/panel_dropdown.py +++ b/weave/legacy/weave/panels/panel_dropdown.py @@ -2,7 +2,7 @@ import typing import weave -from weave.legacy import graph, panel, panel_util +from weave.legacy.weave import graph, panel, panel_util @weave.type() @@ -28,7 +28,7 @@ def __init__( self.config = DropdownConfig() if "choices" in options: self.config.choices = options["choices"] - if isinstance(self.input_node, weave.legacy.graph.VoidNode): + if isinstance(self.input_node, weave.legacy.weave.graph.VoidNode): choices_type = typing.cast(weave.types.List, self.config.choices.type) self.input_node = weave_internal.const( [], weave.types.List(choices_type.object_type) diff --git a/weave/legacy/panels/panel_each.py b/weave/legacy/weave/panels/panel_each.py similarity index 82% rename from weave/legacy/panels/panel_each.py rename to weave/legacy/weave/panels/panel_each.py index ad24f7646a6..6ae0188c5e5 100644 --- a/weave/legacy/panels/panel_each.py +++ b/weave/legacy/weave/panels/panel_each.py @@ -2,9 +2,9 @@ import typing import weave -from weave.legacy import graph, panel -from weave.legacy.panels.bank import default_panel_bank_flow_section_config -from weave.legacy.panels.panel_group import PanelBankSectionConfig +from weave.legacy.weave import graph, panel +from weave.legacy.weave.panels.bank import default_panel_bank_flow_section_config +from weave.legacy.weave.panels.panel_group import PanelBankSectionConfig PanelType = typing.TypeVar("PanelType") diff --git a/weave/legacy/panels/panel_each_column.py b/weave/legacy/weave/panels/panel_each_column.py similarity index 84% rename from weave/legacy/panels/panel_each_column.py rename to weave/legacy/weave/panels/panel_each_column.py index 3df7f7c9625..04b6f01aaa4 100644 --- a/weave/legacy/panels/panel_each_column.py +++ b/weave/legacy/weave/panels/panel_each_column.py @@ -2,8 +2,8 @@ import typing import weave -from weave.legacy import graph, panel -from weave.legacy.panels.panel_group import PanelBankSectionConfig +from weave.legacy.weave import graph, panel +from weave.legacy.weave.panels.panel_group import PanelBankSectionConfig RenderType = typing.TypeVar("RenderType") diff --git a/weave/legacy/panels/panel_expression.py b/weave/legacy/weave/panels/panel_expression.py similarity index 68% rename from weave/legacy/panels/panel_expression.py rename to weave/legacy/weave/panels/panel_expression.py index 3b432283e09..0610f093107 100644 --- a/weave/legacy/panels/panel_expression.py +++ b/weave/legacy/weave/panels/panel_expression.py @@ -1,5 +1,5 @@ import weave -from weave.legacy import panel +from weave.legacy.weave import panel @weave.type() diff --git a/weave/legacy/panels/panel_facet.py b/weave/legacy/weave/panels/panel_facet.py similarity index 86% rename from weave/legacy/panels/panel_facet.py rename to weave/legacy/weave/panels/panel_facet.py index 54e079f333f..bfaa9c3378f 100644 --- a/weave/legacy/panels/panel_facet.py +++ b/weave/legacy/weave/panels/panel_facet.py @@ -2,11 +2,11 @@ import typing import weave -from weave import weave_internal -from weave.legacy import weave_types as types -from weave.legacy import graph, panel, panel_util -from weave.legacy.arrow import list_ -from weave.legacy.panels import table_state +from weave.legacy.weave import weave_internal +from weave.legacy.weave import weave_types as types +from weave.legacy.weave import graph, panel, panel_util +from weave.legacy.weave.arrow import list_ +from weave.legacy.weave.panels import table_state @weave.type() @@ -38,10 +38,10 @@ class FacetConfig: default_factory=lambda: None ) xAxisLabel: weave.Node[str] = dataclasses.field( - default_factory=lambda: weave.legacy.graph.VoidNode() + default_factory=lambda: weave.legacy.weave.graph.VoidNode() ) yAxisLabel: weave.Node[str] = dataclasses.field( - default_factory=lambda: weave.legacy.graph.VoidNode() + default_factory=lambda: weave.legacy.weave.graph.VoidNode() ) @@ -120,14 +120,14 @@ def selected(self): ) x_fn = self.config.table.columnSelectFunctions[self.config.dims.x] y_fn = self.config.table.columnSelectFunctions[self.config.dims.y] - filtered = weave.legacy.ops.List.filter( + filtered = weave.legacy.weave.ops.List.filter( self.input_node, - lambda item: weave.legacy.ops.Boolean.bool_and( - weave.legacy.ops.String.__eq__( + lambda item: weave.legacy.weave.ops.Boolean.bool_and( + weave.legacy.weave.ops.String.__eq__( x_fn, self.config.selectedCell["x"], ), - weave.legacy.ops.String.__eq__( + weave.legacy.weave.ops.String.__eq__( y_fn, self.config.selectedCell["y"], ), diff --git a/weave/legacy/panels/panel_facet_tabs.py b/weave/legacy/weave/panels/panel_facet_tabs.py similarity index 73% rename from weave/legacy/panels/panel_facet_tabs.py rename to weave/legacy/weave/panels/panel_facet_tabs.py index 799c84dbccb..2a62ebea446 100644 --- a/weave/legacy/panels/panel_facet_tabs.py +++ b/weave/legacy/weave/panels/panel_facet_tabs.py @@ -2,8 +2,8 @@ import typing import weave -from weave.legacy import graph, panel -from weave.legacy.panels.panel_group import PanelBankSectionConfig +from weave.legacy.weave import graph, panel +from weave.legacy.weave.panels.panel_group import PanelBankSectionConfig RenderType = typing.TypeVar("RenderType") @@ -11,7 +11,7 @@ @weave.type() class FacetTabsConfig(typing.Generic[RenderType]): tab: weave.Node[typing.Optional[typing.Any]] = dataclasses.field( - default_factory=lambda: weave.legacy.graph.VoidNode() + default_factory=lambda: weave.legacy.weave.graph.VoidNode() ) panel: RenderType = dataclasses.field(default_factory=lambda: graph.VoidNode()) # type: ignore diff --git a/weave/legacy/panels/panel_filter_editor.py b/weave/legacy/weave/panels/panel_filter_editor.py similarity index 94% rename from weave/legacy/panels/panel_filter_editor.py rename to weave/legacy/weave/panels/panel_filter_editor.py index ccf5ceca6e8..c04e3b07818 100644 --- a/weave/legacy/panels/panel_filter_editor.py +++ b/weave/legacy/weave/panels/panel_filter_editor.py @@ -2,7 +2,7 @@ import typing import weave -from weave.legacy import graph, panel +from weave.legacy.weave import graph, panel ExpressionType = typing.TypeVar("ExpressionType") diff --git a/weave/legacy/panels/panel_function_editor.py b/weave/legacy/weave/panels/panel_function_editor.py similarity index 91% rename from weave/legacy/panels/panel_function_editor.py rename to weave/legacy/weave/panels/panel_function_editor.py index d8b5e2a13b2..dc3f3f488f3 100644 --- a/weave/legacy/panels/panel_function_editor.py +++ b/weave/legacy/weave/panels/panel_function_editor.py @@ -2,7 +2,7 @@ import typing import weave -from weave.legacy import graph, panel +from weave.legacy.weave import graph, panel ExpressionType = typing.TypeVar("ExpressionType") diff --git a/weave/legacy/panels/panel_group.py b/weave/legacy/weave/panels/panel_group.py similarity index 97% rename from weave/legacy/panels/panel_group.py rename to weave/legacy/weave/panels/panel_group.py index a7ea1847900..54e7566e501 100644 --- a/weave/legacy/panels/panel_group.py +++ b/weave/legacy/weave/panels/panel_group.py @@ -3,8 +3,8 @@ import typing import weave -from weave import weave_internal -from weave.legacy import ( +from weave.legacy.weave import weave_internal +from weave.legacy.weave import ( errors, codifiable_value_mixin, codify, @@ -13,11 +13,11 @@ panel, panel_util, ) -from weave.legacy.panels.bank import ( +from weave.legacy.weave.panels.bank import ( default_panel_bank_flow_section_config, flow_layout, ) -from weave.legacy.panels.panel_group_panel_info import PanelInfo +from weave.legacy.weave.panels.panel_group_panel_info import PanelInfo ItemsType = typing.TypeVar("ItemsType") @@ -337,7 +337,7 @@ def to_code(self) -> typing.Optional[str]: ",".join([f_name + "=" + f_val for f_name, f_val in field_vals]) + "," ) return ( - f"""weave.legacy.panels.panel_group.Group({input_node_str} {param_str})""" + f"""weave.legacy.weave.panels.panel_group.Group({input_node_str} {param_str})""" ) # @property diff --git a/weave/legacy/panels/panel_group_panel_info.py b/weave/legacy/weave/panels/panel_group_panel_info.py similarity index 100% rename from weave/legacy/panels/panel_group_panel_info.py rename to weave/legacy/weave/panels/panel_group_panel_info.py diff --git a/weave/legacy/panels/panel_grouping_editor.py b/weave/legacy/weave/panels/panel_grouping_editor.py similarity index 94% rename from weave/legacy/panels/panel_grouping_editor.py rename to weave/legacy/weave/panels/panel_grouping_editor.py index ebaa9a7e7fd..ce608643040 100644 --- a/weave/legacy/panels/panel_grouping_editor.py +++ b/weave/legacy/weave/panels/panel_grouping_editor.py @@ -2,7 +2,7 @@ import typing import weave -from weave.legacy import graph, panel +from weave.legacy.weave import graph, panel ExpressionType = typing.TypeVar("ExpressionType") diff --git a/weave/legacy/panels/panel_histogram.py b/weave/legacy/weave/panels/panel_histogram.py similarity index 86% rename from weave/legacy/panels/panel_histogram.py rename to weave/legacy/weave/panels/panel_histogram.py index 6daa14a64bb..79a91e9f726 100644 --- a/weave/legacy/panels/panel_histogram.py +++ b/weave/legacy/weave/panels/panel_histogram.py @@ -1,5 +1,5 @@ import weave -from weave.legacy import panel +from weave.legacy.weave import panel # TODO: This id conflicts with the histogram type! Panel types # should automatically have Panel in the name but they don't at the moment. diff --git a/weave/legacy/panels/panel_html.py b/weave/legacy/weave/panels/panel_html.py similarity index 90% rename from weave/legacy/panels/panel_html.py rename to weave/legacy/weave/panels/panel_html.py index 49d9d5b214a..bba1afb0b55 100644 --- a/weave/legacy/panels/panel_html.py +++ b/weave/legacy/weave/panels/panel_html.py @@ -1,5 +1,5 @@ import weave -from weave.legacy import graph, ops, panel, panel_util +from weave.legacy.weave import graph, ops, panel, panel_util @weave.type() diff --git a/weave/legacy/panels/panel_labeled_item.py b/weave/legacy/weave/panels/panel_labeled_item.py similarity index 93% rename from weave/legacy/panels/panel_labeled_item.py rename to weave/legacy/weave/panels/panel_labeled_item.py index 040038accb6..08c5703dc7f 100644 --- a/weave/legacy/panels/panel_labeled_item.py +++ b/weave/legacy/weave/panels/panel_labeled_item.py @@ -2,7 +2,7 @@ import typing import weave -from weave.legacy import graph, panel, panel_util +from weave.legacy.weave import graph, panel, panel_util ItemType = typing.TypeVar("ItemType") diff --git a/weave/legacy/panels/panel_legacy.py b/weave/legacy/weave/panels/panel_legacy.py similarity index 98% rename from weave/legacy/panels/panel_legacy.py rename to weave/legacy/weave/panels/panel_legacy.py index 4673c7ab3e8..9e7c255dd1d 100644 --- a/weave/legacy/panels/panel_legacy.py +++ b/weave/legacy/weave/panels/panel_legacy.py @@ -73,7 +73,7 @@ class LPanel: def define_panel(p: LPanel): - class DummyClass(weave.legacy.panel.Panel): + class DummyClass(weave.legacy.weave.panel.Panel): id = p.panel_id type_cache.append(DummyClass) diff --git a/weave/legacy/panels/panel_markdown.py b/weave/legacy/weave/panels/panel_markdown.py similarity index 90% rename from weave/legacy/panels/panel_markdown.py rename to weave/legacy/weave/panels/panel_markdown.py index f86f5b8b1a8..fc489f6efc3 100644 --- a/weave/legacy/panels/panel_markdown.py +++ b/weave/legacy/weave/panels/panel_markdown.py @@ -1,5 +1,5 @@ import weave -from weave.legacy import graph, ops, panel, panel_util +from weave.legacy.weave import graph, ops, panel, panel_util @weave.type() diff --git a/weave/legacy/panels/panel_object_picker.py b/weave/legacy/weave/panels/panel_object_picker.py similarity index 95% rename from weave/legacy/panels/panel_object_picker.py rename to weave/legacy/weave/panels/panel_object_picker.py index 802afc109a2..35a7321166c 100644 --- a/weave/legacy/panels/panel_object_picker.py +++ b/weave/legacy/weave/panels/panel_object_picker.py @@ -2,8 +2,8 @@ import typing import weave -from weave import weave_internal -from weave.legacy import graph, panel +from weave.legacy.weave import weave_internal +from weave.legacy.weave import graph, panel ChoiceType = typing.TypeVar("ChoiceType") diff --git a/weave/legacy/panels/panel_plot.py b/weave/legacy/weave/panels/panel_plot.py similarity index 98% rename from weave/legacy/panels/panel_plot.py rename to weave/legacy/weave/panels/panel_plot.py index 7210836b2d7..a14469bec3a 100644 --- a/weave/legacy/panels/panel_plot.py +++ b/weave/legacy/weave/panels/panel_plot.py @@ -4,12 +4,12 @@ import typing import weave -from weave import weave_internal -from weave.legacy import weave_types as types -from weave.legacy import errors, codifiable_value_mixin, codify, graph, panel -from weave.legacy.ops_primitives import boolean, list_ -from weave.legacy.ops_primitives import dict as dict_ -from weave.legacy.panels import panel_table, table_state +from weave.legacy.weave import weave_internal +from weave.legacy.weave import weave_types as types +from weave.legacy.weave import errors, codifiable_value_mixin, codify, graph, panel +from weave.legacy.weave.ops_primitives import boolean, list_ +from weave.legacy.weave.ops_primitives import dict as dict_ +from weave.legacy.weave.panels import panel_table, table_state @weave.type() @@ -696,7 +696,7 @@ def to_code(self) -> typing.Optional[str]: param_str = ( ",".join([f_name + "=" + f_val for f_name, f_val in field_vals]) + "," ) - return f"""weave.legacy.panels.panel_plot.Plot({codify.object_to_code_no_format(self.input_node)}, {param_str})""" + return f"""weave.legacy.weave.panels.panel_plot.Plot({codify.object_to_code_no_format(self.input_node)}, {param_str})""" def make_set_all_series(dim_name: str) -> typing.Callable[[Plot, typing.Any], None]: diff --git a/weave/legacy/panels/panel_query.py b/weave/legacy/weave/panels/panel_query.py similarity index 89% rename from weave/legacy/panels/panel_query.py rename to weave/legacy/weave/panels/panel_query.py index 1ca55a02828..0336cd9e0f7 100644 --- a/weave/legacy/panels/panel_query.py +++ b/weave/legacy/weave/panels/panel_query.py @@ -2,9 +2,9 @@ import typing import weave -from weave import weave_internal -from weave.legacy import graph, panel -from weave.legacy.panels import table_state +from weave.legacy.weave import weave_internal +from weave.legacy.weave import graph, panel +from weave.legacy.weave.panels import table_state @weave.type() @@ -18,7 +18,7 @@ class QueryDimsConfig: @weave.type() class QueryCondition: expression: weave.Node[typing.Any] = dataclasses.field( - default_factory=lambda: weave.legacy.graph.VoidNode() + default_factory=lambda: weave.legacy.weave.graph.VoidNode() ) editor: EditorType = dataclasses.field(default_factory=lambda: graph.VoidNode()) # type: ignore @@ -76,7 +76,7 @@ def selected(self): and self.config.tableState.preFilterFunction is not None and self.config.tableState.preFilterFunction.type != weave.types.Invalid() ): - table_node = weave.legacy.ops.List.filter( + table_node = weave.legacy.weave.ops.List.filter( table_node, lambda row: weave_internal.call_fn( self.config.tableState.preFilterFunction, {"row": row} diff --git a/weave/legacy/panels/panel_sections.py b/weave/legacy/weave/panels/panel_sections.py similarity index 73% rename from weave/legacy/panels/panel_sections.py rename to weave/legacy/weave/panels/panel_sections.py index 5808d0ad407..e23cbfff43b 100644 --- a/weave/legacy/panels/panel_sections.py +++ b/weave/legacy/weave/panels/panel_sections.py @@ -2,8 +2,8 @@ import typing import weave -from weave.legacy import graph, panel -from weave.legacy.panels.panel_group import PanelBankSectionConfig +from weave.legacy.weave import graph, panel +from weave.legacy.weave.panels.panel_group import PanelBankSectionConfig RenderType = typing.TypeVar("RenderType") @@ -11,7 +11,7 @@ @weave.type() class SectionsConfig(typing.Generic[RenderType]): section: weave.Node[typing.Optional[typing.Any]] = dataclasses.field( - default_factory=lambda: weave.legacy.graph.VoidNode() + default_factory=lambda: weave.legacy.weave.graph.VoidNode() ) panel: RenderType = dataclasses.field(default_factory=lambda: graph.VoidNode()) # type: ignore diff --git a/weave/legacy/panels/panel_select.py b/weave/legacy/weave/panels/panel_select.py similarity index 85% rename from weave/legacy/panels/panel_select.py rename to weave/legacy/weave/panels/panel_select.py index b62334154da..07ca97bde51 100644 --- a/weave/legacy/panels/panel_select.py +++ b/weave/legacy/weave/panels/panel_select.py @@ -2,8 +2,8 @@ import typing import weave -from weave import weave_internal -from weave.legacy import graph, panel +from weave.legacy.weave import weave_internal +from weave.legacy.weave import graph, panel @weave.type() @@ -27,7 +27,7 @@ def __init__(self, input_node=graph.VoidNode(), vars=None, config=None, **option self.config = SelectEditorConfig() if "choices" in options: self.config.choices = options["choices"] - if isinstance(self.input_node, weave.legacy.graph.VoidNode): + if isinstance(self.input_node, weave.legacy.weave.graph.VoidNode): # TODO: not string! self.input_node = weave_internal.const( [], weave.types.List(self.config.choices.type.object_type) diff --git a/weave/legacy/panels/panel_sidebarcontainer.py b/weave/legacy/weave/panels/panel_sidebarcontainer.py similarity index 89% rename from weave/legacy/panels/panel_sidebarcontainer.py rename to weave/legacy/weave/panels/panel_sidebarcontainer.py index f568b6649f8..23ec5927233 100644 --- a/weave/legacy/panels/panel_sidebarcontainer.py +++ b/weave/legacy/weave/panels/panel_sidebarcontainer.py @@ -1,5 +1,5 @@ -from weave.legacy import graph -from weave.legacy.panels import panel +from weave.legacy.weave import graph +from weave.legacy.weave.panels import panel class VerticalContainerConfig: diff --git a/weave/legacy/panels/panel_slider.py b/weave/legacy/weave/panels/panel_slider.py similarity index 85% rename from weave/legacy/panels/panel_slider.py rename to weave/legacy/weave/panels/panel_slider.py index 130407577d1..b7eef8aceb4 100644 --- a/weave/legacy/panels/panel_slider.py +++ b/weave/legacy/weave/panels/panel_slider.py @@ -2,8 +2,8 @@ import typing import weave -from weave import weave_internal -from weave.legacy import panel +from weave.legacy.weave import weave_internal +from weave.legacy.weave import panel @weave.type() @@ -28,7 +28,7 @@ class Slider(panel.Panel): def __post_init__(self, *args): super().__post_init__(*args) - if isinstance(self.input_node, weave.legacy.graph.VoidNode): + if isinstance(self.input_node, weave.legacy.weave.graph.VoidNode): self.__dict__["input_node"] = weave_internal.const(0) @weave.op() diff --git a/weave/legacy/panels/panel_string.py b/weave/legacy/weave/panels/panel_string.py similarity index 94% rename from weave/legacy/panels/panel_string.py rename to weave/legacy/weave/panels/panel_string.py index 0b61a56fe03..58cecb54794 100644 --- a/weave/legacy/panels/panel_string.py +++ b/weave/legacy/weave/panels/panel_string.py @@ -2,7 +2,7 @@ import typing import weave -from weave.legacy import panel +from weave.legacy.weave import panel ModeOption = typing.Literal["plaintext", "markdown", "diff"] diff --git a/weave/legacy/panels/panel_string_editor.py b/weave/legacy/weave/panels/panel_string_editor.py similarity index 82% rename from weave/legacy/panels/panel_string_editor.py rename to weave/legacy/weave/panels/panel_string_editor.py index a6f6bdae949..3aeb0f440d6 100644 --- a/weave/legacy/panels/panel_string_editor.py +++ b/weave/legacy/weave/panels/panel_string_editor.py @@ -1,5 +1,5 @@ import weave -from weave.legacy import panel +from weave.legacy.weave import panel @weave.type() diff --git a/weave/legacy/panels/panel_table.py b/weave/legacy/weave/panels/panel_table.py similarity index 93% rename from weave/legacy/panels/panel_table.py rename to weave/legacy/weave/panels/panel_table.py index 4cce8bfb05c..e9fb6b0760d 100644 --- a/weave/legacy/panels/panel_table.py +++ b/weave/legacy/weave/panels/panel_table.py @@ -2,11 +2,11 @@ import typing import weave -from weave import weave_internal -from weave.legacy import codifiable_value_mixin, codify, panel -from weave.legacy.graph import ConstNode, Node, OutputNode, VoidNode -from weave.legacy.panels import table_state -from weave.legacy.panels.panel_query import Query +from weave.legacy.weave import weave_internal +from weave.legacy.weave import codifiable_value_mixin, codify, panel +from weave.legacy.weave.graph import ConstNode, Node, OutputNode, VoidNode +from weave.legacy.weave.panels import table_state +from weave.legacy.weave.panels.panel_query import Query @weave.type("tablePanelConfig") @@ -20,7 +20,7 @@ class TableConfig: class ColumnDef(typing.TypedDict): columnName: str - columnSelectFunction: weave.legacy.graph.Node + columnSelectFunction: weave.legacy.weave.graph.Node @dataclasses.dataclass @@ -128,7 +128,7 @@ def to_code(self) -> typing.Optional[str]: param_str = ( ",".join([f_name + "=" + f_val for f_name, f_val in field_vals]) + "," ) - return f"""weave.legacy.panels.panel_table.Table({codify.object_to_code_no_format(self.input_node)}, {param_str})""" + return f"""weave.legacy.weave.panels.panel_table.Table({codify.object_to_code_no_format(self.input_node)}, {param_str})""" def add_column( self, @@ -165,14 +165,14 @@ def _get_composite_group_key(self: typing.Union[Table, Query]) -> str: # TODO: preserve arrow def _get_pinned_node(self: typing.Union[Table, Query], data_or_rows_node: Node) -> Node: if self.config is None: - return weave.legacy.ops.make_list() + return weave.legacy.weave.ops.make_list() composite_group_key = _get_composite_group_key(self) pinned_data = self.config.pinnedRows.get(composite_group_key) if pinned_data is None or len(pinned_data) == 0: - return weave.legacy.ops.make_list() + return weave.legacy.weave.ops.make_list() - return weave.legacy.ops.make_list( + return weave.legacy.weave.ops.make_list( **{ f"v_{pin_ndx}": OutputNode( data_or_rows_node.type, @@ -213,7 +213,7 @@ def _get_rows_node(self: Table, apply_sort: bool = True) -> Node: and self.config.tableState.preFilterFunction is not None and self.config.tableState.preFilterFunction.type != weave.types.Invalid() ): - data_node = weave.legacy.ops.List.filter( + data_node = weave.legacy.weave.ops.List.filter( data_node, lambda row, index: weave_internal.call_fn( self.config.tableState.preFilterFunction, {"row": row, "index": index} @@ -226,9 +226,9 @@ def _get_rows_node(self: Table, apply_sort: bool = True) -> Node: group_ids: typing.Set[str] = set() if self.config and self.config.tableState.groupBy: group_ids = set(self.config.tableState.groupBy) - data_node = weave.legacy.ops.List.groupby( + data_node = weave.legacy.weave.ops.List.groupby( data_node, - lambda row: weave.legacy.ops.dict_( + lambda row: weave.legacy.weave.ops.dict_( **{ columns[col_id]["columnName"]: weave_internal.call_fn( columns[col_id]["columnSelectFunction"], @@ -242,9 +242,9 @@ def _get_rows_node(self: Table, apply_sort: bool = True) -> Node: ) # Apply Selection - data_node = weave.legacy.ops.List.map( + data_node = weave.legacy.weave.ops.List.map( data_node, - lambda row, index: weave.legacy.ops.dict_( + lambda row, index: weave.legacy.weave.ops.dict_( **{ col_def["columnName"]: ( weave_internal.call_fn( @@ -275,9 +275,9 @@ def make_sort_fn(sort_def, row_node): # else: # return row_node[col_name] - data_node = weave.legacy.ops.List.sort( + data_node = weave.legacy.weave.ops.List.sort( data_node, - lambda row: weave.legacy.ops.make_list( + lambda row: weave.legacy.weave.ops.make_list( **{ f"{sort_ndx}": make_sort_fn(sort_def, row) for sort_ndx, sort_def in enumerate(sort_defs) diff --git a/weave/legacy/panels/panel_trace.py b/weave/legacy/weave/panels/panel_trace.py similarity index 94% rename from weave/legacy/panels/panel_trace.py rename to weave/legacy/weave/panels/panel_trace.py index 5c002a4071b..cd8f0ff4f56 100644 --- a/weave/legacy/panels/panel_trace.py +++ b/weave/legacy/weave/panels/panel_trace.py @@ -2,7 +2,7 @@ import typing import weave -from weave.legacy import panel +from weave.legacy.weave import panel span_typed_dict_type = weave.types.TypedDict( { @@ -62,4 +62,4 @@ def __init__(self, input_node, vars=None, config=None, **options): ) def active_span(self: Trace): index = 0 if self.config is None else self.config.selectedSpanIndex - return weave.legacy.ops_arrow.list_ops.index(self.input_node, index) + return weave.legacy.weave.ops_arrow.list_ops.index(self.input_node, index) diff --git a/weave/legacy/panels/panel_trace_span.py b/weave/legacy/weave/panels/panel_trace_span.py similarity index 85% rename from weave/legacy/panels/panel_trace_span.py rename to weave/legacy/weave/panels/panel_trace_span.py index 460d79bcbfa..35b565d6350 100644 --- a/weave/legacy/panels/panel_trace_span.py +++ b/weave/legacy/weave/panels/panel_trace_span.py @@ -1,5 +1,5 @@ import weave -from weave.legacy import panel +from weave.legacy.weave import panel @weave.type("traceSpanPanel") diff --git a/weave/legacy/panels/panel_weavelink.py b/weave/legacy/weave/panels/panel_weavelink.py similarity index 89% rename from weave/legacy/panels/panel_weavelink.py rename to weave/legacy/weave/panels/panel_weavelink.py index ac1bb125869..1d822212323 100644 --- a/weave/legacy/panels/panel_weavelink.py +++ b/weave/legacy/weave/panels/panel_weavelink.py @@ -2,14 +2,14 @@ import typing import weave -from weave import weave_internal -from weave.legacy import graph, ops, panel, panel_util +from weave.legacy.weave import weave_internal +from weave.legacy.weave import graph, ops, panel, panel_util @weave.type() class WeaveLinkConfig: to: weave.Node[typing.Optional[typing.Any]] = dataclasses.field( - default_factory=lambda: weave.legacy.graph.VoidNode() + default_factory=lambda: weave.legacy.weave.graph.VoidNode() ) vars: typing.Dict[str, graph.Node] = dataclasses.field(default_factory=dict) diff --git a/weave/legacy/panels/table_state.py b/weave/legacy/weave/panels/table_state.py similarity index 97% rename from weave/legacy/panels/table_state.py rename to weave/legacy/weave/panels/table_state.py index 9415c814d7d..0a333efe967 100644 --- a/weave/legacy/panels/table_state.py +++ b/weave/legacy/weave/panels/table_state.py @@ -7,9 +7,9 @@ import string import typing -from weave import weave_internal -from weave.legacy import decorator_type, graph, ops, panel, weave_types -from weave.legacy.language_features.tagging import tagged_value_type +from weave.legacy.weave import weave_internal +from weave.legacy.weave import decorator_type, graph, ops, panel, weave_types +from weave.legacy.weave.language_features.tagging import tagged_value_type @decorator_type.type() diff --git a/weave/legacy/panels_py/__init__.py b/weave/legacy/weave/panels_py/__init__.py similarity index 64% rename from weave/legacy/panels_py/__init__.py rename to weave/legacy/weave/panels_py/__init__.py index 3b262288025..90abece1622 100644 --- a/weave/legacy/panels_py/__init__.py +++ b/weave/legacy/weave/panels_py/__init__.py @@ -1,8 +1,8 @@ -from weave.legacy import context_state as _context_state +from weave.legacy.weave import context_state as _context_state _loading_builtins_token = _context_state.set_loading_built_ins() -from weave.legacy.panels_py import ( +from weave.legacy.weave.panels_py import ( generator_templates, panel_autoboard, panel_llm_monitor, @@ -12,6 +12,6 @@ ) # This is just an example, uncomment to enable -# from weave.legacy.panels_py import panel_example_custom_board +# from weave.legacy.weave.panels_py import panel_example_custom_board _context_state.clear_loading_built_ins(_loading_builtins_token) diff --git a/weave/legacy/panels_py/generator_templates.py b/weave/legacy/weave/panels_py/generator_templates.py similarity index 97% rename from weave/legacy/panels_py/generator_templates.py rename to weave/legacy/weave/panels_py/generator_templates.py index 2aa6e524424..63cf1d0c6c8 100644 --- a/weave/legacy/panels_py/generator_templates.py +++ b/weave/legacy/weave/panels_py/generator_templates.py @@ -18,8 +18,8 @@ import dataclasses import typing -from weave.legacy import weave_types -from weave.legacy import decorator_op, graph, registry_mem +from weave.legacy.weave import weave_types +from weave.legacy.weave import decorator_op, graph, registry_mem @dataclasses.dataclass diff --git a/weave/legacy/panels_py/instructions/panel_autoboard.md b/weave/legacy/weave/panels_py/instructions/panel_autoboard.md similarity index 100% rename from weave/legacy/panels_py/instructions/panel_autoboard.md rename to weave/legacy/weave/panels_py/instructions/panel_autoboard.md diff --git a/weave/legacy/panels_py/instructions/panel_llm_monitor.md b/weave/legacy/weave/panels_py/instructions/panel_llm_monitor.md similarity index 100% rename from weave/legacy/panels_py/instructions/panel_llm_monitor.md rename to weave/legacy/weave/panels_py/instructions/panel_llm_monitor.md diff --git a/weave/legacy/panels_py/instructions/panel_trace_monitor.md b/weave/legacy/weave/panels_py/instructions/panel_trace_monitor.md similarity index 93% rename from weave/legacy/panels_py/instructions/panel_trace_monitor.md rename to weave/legacy/weave/panels_py/instructions/panel_trace_monitor.md index 5a3dd39d29b..4f49b229fb0 100644 --- a/weave/legacy/panels_py/instructions/panel_trace_monitor.md +++ b/weave/legacy/weave/panels_py/instructions/panel_trace_monitor.md @@ -25,7 +25,7 @@ pip install weave If you are already using `Langchain`, simply create a tracer and add it as a callback to your next call: ```python -from weave.legacy.monitoring.langchain import WeaveTracer +from weave.legacy.weave.monitoring.langchain import WeaveTracer tracer = WeaveTracer(f"{WB_ENTITY}/{WB_PROJECT}/{WB_STREAM}") llm.run(question, callbacks=[tracer]) @@ -38,7 +38,7 @@ llm.run(question, callbacks=[tracer]) If you have existing code, the Trace decorator (and related utilities) allows you to instrument and log in a variety of formats. For example: ```python -from weave.legacy.monitoring import init_monitor +from weave.legacy.weave.monitoring import init_monitor mon = init_monitor(f"{WB_ENTITY}/{WB_PROJECT}/{WB_STREAM}") @@ -55,7 +55,7 @@ def adder(a, b): Finally, if you want to manually log span data, you can do so as well by logging directly to a StreamTable: ```python -from weave.legacy.monitoring import StreamTable +from weave.legacy.weave.monitoring import StreamTable from weave.stream_data_interfaces import TraceSpanDict st = StreamTable(f"{WB_ENTITY}/{WB_PROJECT}/{WB_STREAM}") diff --git a/weave/legacy/panels_py/panel_autoboard.py b/weave/legacy/weave/panels_py/panel_autoboard.py similarity index 84% rename from weave/legacy/panels_py/panel_autoboard.py rename to weave/legacy/weave/panels_py/panel_autoboard.py index ab37b9813ac..810bfa88fc4 100644 --- a/weave/legacy/panels_py/panel_autoboard.py +++ b/weave/legacy/weave/panels_py/panel_autoboard.py @@ -26,10 +26,10 @@ import typing import weave -from weave import weave_internal -from weave.legacy.panels import panel_plot -from weave.legacy.panels_py.generator_templates import template_registry -from weave.legacy import util +from weave.legacy.weave import weave_internal +from weave.legacy.weave.panels import panel_plot +from weave.legacy.weave.panels_py.generator_templates import template_registry +from weave.legacy.weave import util @weave.type() @@ -72,10 +72,10 @@ def timeseries( x_axis_type = input_node[x_axis_key].type.object_type # type: ignore if weave.types.optional(weave.types.Timestamp()).assign_type(x_axis_type): x_title = "" - bin_fn = weave.legacy.ops.timestamp_bins_nice + bin_fn = weave.legacy.weave.ops.timestamp_bins_nice elif weave.types.optional(weave.types.Number()).assign_type(x_axis_type): x_title = x_axis_key - bin_fn = weave.legacy.ops.numbers_bins_equal + bin_fn = weave.legacy.weave.ops.numbers_bins_equal else: raise ValueError(f"Unsupported type for x_axis_key {x_axis_key}: {x_axis_type}") if mark == "bar": @@ -84,7 +84,7 @@ def timeseries( # TODO: should be midpoint x = lambda row: row[x_axis_key].bin(bin_fn(bin_domain_node, n_bins))["start"] - return weave.legacy.panels.Plot( + return weave.legacy.weave.panels.Plot( input_node, x=x, x_title=x_title, @@ -108,12 +108,12 @@ def timeseries_avg_line( ) -> weave.Panel: x_axis_type = input_node[x_axis_key].type.object_type # type: ignore if weave.types.optional(weave.types.Timestamp()).assign_type(x_axis_type): - bin_fn = weave.legacy.ops.timestamp_bins_nice + bin_fn = weave.legacy.weave.ops.timestamp_bins_nice elif weave.types.optional(weave.types.Number()).assign_type(x_axis_type): - bin_fn = weave.legacy.ops.numbers_bins_equal + bin_fn = weave.legacy.weave.ops.numbers_bins_equal else: raise ValueError(f"Unsupported type for x_axis_key {x_axis_key}: {x_axis_type}") - return weave.legacy.panels.Plot( + return weave.legacy.weave.panels.Plot( input_node, x=lambda row: row[x_axis_key].bin(bin_fn(bin_domain_node, 100))["start"], x_title=x_axis_key, @@ -139,13 +139,13 @@ def timeseries_sum_bar( x_axis_type = input_node[x_axis_key].type.object_type # type: ignore if weave.types.optional(weave.types.Timestamp()).assign_type(x_axis_type): x_title = "" - bin_fn = weave.legacy.ops.timestamp_bins_nice + bin_fn = weave.legacy.weave.ops.timestamp_bins_nice elif weave.types.optional(weave.types.Number()).assign_type(x_axis_type): x_title = x_axis_key - bin_fn = weave.legacy.ops.numbers_bins_equal + bin_fn = weave.legacy.weave.ops.numbers_bins_equal else: raise ValueError(f"Unsupported type for x_axis_key {x_axis_key}: {x_axis_type}") - return weave.legacy.panels.Plot( + return weave.legacy.weave.panels.Plot( input_node, x=lambda row: row[x_axis_key].bin(bin_fn(bin_domain_node, n_bins)), x_title=x_title, @@ -170,13 +170,13 @@ def timeseries_count_bar( x_axis_type = input_node[x_axis_key].type.object_type # type: ignore if weave.types.optional(weave.types.Timestamp()).assign_type(x_axis_type): x_title = "" - bin_fn = weave.legacy.ops.timestamp_bins_nice + bin_fn = weave.legacy.weave.ops.timestamp_bins_nice elif weave.types.optional(weave.types.Number()).assign_type(x_axis_type): x_title = x_axis_key - bin_fn = weave.legacy.ops.numbers_bins_equal + bin_fn = weave.legacy.weave.ops.numbers_bins_equal else: raise ValueError(f"Unsupported type for x_axis_key {x_axis_key}: {x_axis_type}") - return weave.legacy.panels.Plot( + return weave.legacy.weave.panels.Plot( input_node, x=lambda row: row[x_axis_key].bin(bin_fn(bin_domain_node, n_bins)), x_title=x_title, @@ -192,7 +192,7 @@ def categorical_dist( input_node: weave.Node[list[typing.Any]], key: str, ) -> weave.Panel: - return weave.legacy.panels.Plot( + return weave.legacy.weave.panels.Plot( input_node, y=lambda row: row[key], x=lambda row: row.count(), @@ -317,49 +317,49 @@ def auto_panels( panel = categorical_dist(window_data_node, key) categorical_panels.append(panel) - metrics = weave.legacy.panels.Group( - layoutMode=weave.legacy.panels.GroupLayoutFlow(2, 3), + metrics = weave.legacy.weave.panels.Group( + layoutMode=weave.legacy.weave.panels.GroupLayoutFlow(2, 3), items={"panel%s" % i: panel for i, panel in enumerate(metric_panels)}, ) - categoricals = weave.legacy.panels.Group( - layoutMode=weave.legacy.panels.GroupLayoutFlow(2, 3), + categoricals = weave.legacy.weave.panels.Group( + layoutMode=weave.legacy.weave.panels.GroupLayoutFlow(2, 3), items={"panel%s" % i: panel for i, panel in enumerate(categorical_panels)}, ) control_items = [ - weave.legacy.panels.GroupPanel( + weave.legacy.weave.panels.GroupPanel( input_node, id="data", ), # TODO: We need a filter editor. Can start with a filter expression # editor and make it more user-friendly later - weave.legacy.panels.GroupPanel( - lambda data: weave.legacy.ops.make_list( + weave.legacy.weave.panels.GroupPanel( + lambda data: weave.legacy.weave.ops.make_list( a=data[x_axis].min(), b=data[x_axis].max() ), id="data_range", hidden=True, ), - weave.legacy.panels.GroupPanel(None, id="zoom_range", hidden=True), - weave.legacy.panels.GroupPanel( + weave.legacy.weave.panels.GroupPanel(None, id="zoom_range", hidden=True), + weave.legacy.weave.panels.GroupPanel( lambda zoom_range, data_range: zoom_range.coalesce(data_range), id="bin_range", hidden=True, ), - weave.legacy.panels.GroupPanel( - lambda data, zoom_range: weave.legacy.panels.DateRange( + weave.legacy.weave.panels.GroupPanel( + lambda data, zoom_range: weave.legacy.weave.panels.DateRange( zoom_range, domain=data[x_axis] ), id="date_picker", ), # TODO: groupby should really be a Dropdown / multi-select instead # of an expression - weave.legacy.panels.GroupPanel( + weave.legacy.weave.panels.GroupPanel( groupby, id="groupby", ), - weave.legacy.panels.GroupPanel( + weave.legacy.weave.panels.GroupPanel( lambda data, bin_range: data.filter( - lambda row: weave.legacy.ops.Boolean.bool_and( + lambda row: weave.legacy.weave.ops.Boolean.bool_and( row[x_axis] >= bin_range[0], row[x_axis] < bin_range[1] ) ), @@ -369,21 +369,21 @@ def auto_panels( ] panels = [ - weave.legacy.panels.BoardPanel( + weave.legacy.weave.panels.BoardPanel( metrics, id="metrics", - layout=weave.legacy.panels.BoardPanelLayout(x=0, y=0, w=24, h=12), + layout=weave.legacy.weave.panels.BoardPanelLayout(x=0, y=0, w=24, h=12), ), - weave.legacy.panels.BoardPanel( + weave.legacy.weave.panels.BoardPanel( categoricals, id="categoricals", - layout=weave.legacy.panels.BoardPanelLayout(x=0, y=0, w=24, h=12), + layout=weave.legacy.weave.panels.BoardPanelLayout(x=0, y=0, w=24, h=12), ), ] if "step" not in x_axis: panels.insert( 0, - weave.legacy.panels.BoardPanel( + weave.legacy.weave.panels.BoardPanel( timeseries_count_bar( data_node, time_domain_node, @@ -393,18 +393,18 @@ def auto_panels( 150, ), id="volume", - layout=weave.legacy.panels.BoardPanelLayout(x=0, y=0, w=24, h=6), + layout=weave.legacy.weave.panels.BoardPanelLayout(x=0, y=0, w=24, h=6), ), ) panels.append( - weave.legacy.panels.BoardPanel( + weave.legacy.weave.panels.BoardPanel( window_data_node, id="table", - layout=weave.legacy.panels.BoardPanelLayout(x=0, y=0, w=24, h=6), + layout=weave.legacy.weave.panels.BoardPanelLayout(x=0, y=0, w=24, h=6), ), ) - return weave.legacy.panels.Board(vars=control_items, panels=panels, editable=False) + return weave.legacy.weave.panels.Board(vars=control_items, panels=panels, editable=False) # The interface for constructing this Panel from Python @@ -415,7 +415,7 @@ class AutoBoard(weave.Panel): config: typing.Optional[AutoBoardConfig] = None @weave.op() # type: ignore - def render(self) -> weave.legacy.panels.Group: + def render(self) -> weave.legacy.weave.panels.Group: return auto_panels(self.input_node, self.config) # type: ignore @@ -426,7 +426,7 @@ def render(self) -> weave.legacy.panels.Group: def seed_autoboard( input_node: weave.Node[typing.Any], config: typing.Optional[AutoBoardConfig] = None, -) -> weave.legacy.panels.Group: +) -> weave.legacy.weave.panels.Group: return auto_panels(input_node, config) # type: ignore diff --git a/weave/legacy/panels_py/panel_eval.py b/weave/legacy/weave/panels_py/panel_eval.py similarity index 67% rename from weave/legacy/panels_py/panel_eval.py rename to weave/legacy/weave/panels_py/panel_eval.py index 95ec348fa89..3cf7afacd07 100644 --- a/weave/legacy/panels_py/panel_eval.py +++ b/weave/legacy/weave/panels_py/panel_eval.py @@ -1,5 +1,5 @@ import weave -from weave.legacy.panels import panel_board +from weave.legacy.weave.panels import panel_board # This is not yet general, it describes a board for a specific # formulation of a text extraction problem @@ -13,34 +13,34 @@ def eval_board(dataset, eval_result0, eval_result1): # type: ignore summary = varbar.add( "summary", - weave.legacy.ops.make_list( - a=weave.legacy.ops.TypedDict.merge( - weave.legacy.ops.dict_(name="baseline"), + weave.legacy.weave.ops.make_list( + a=weave.legacy.weave.ops.TypedDict.merge( + weave.legacy.weave.ops.dict_(name="baseline"), baseline_eval_result["summary"], ), - b=weave.legacy.ops.TypedDict.merge( - weave.legacy.ops.dict_(name="candidate"), + b=weave.legacy.weave.ops.TypedDict.merge( + weave.legacy.weave.ops.dict_(name="candidate"), candidate_eval_result["summary"], ), ), ) - weave.legacy.ops.make_list( + weave.legacy.weave.ops.make_list( a=baseline_eval_result["eval_table"], b=baseline_eval_result["eval_table"] ) concatted_evals = varbar.add( "concatted_evals", - weave.legacy.ops.List.concat( - weave.legacy.ops.make_list( + weave.legacy.weave.ops.List.concat( + weave.legacy.weave.ops.make_list( a=baseline_eval_result["eval_table"].map( - lambda row: weave.legacy.ops.TypedDict.merge( - weave.legacy.ops.dict_(name="baseline"), row + lambda row: weave.legacy.weave.ops.TypedDict.merge( + weave.legacy.weave.ops.dict_(name="baseline"), row ) ), b=candidate_eval_result["eval_table"].map( - lambda row: weave.legacy.ops.TypedDict.merge( - weave.legacy.ops.dict_(name="candidate"), row + lambda row: weave.legacy.weave.ops.TypedDict.merge( + weave.legacy.weave.ops.dict_(name="candidate"), row ) ), ) @@ -50,8 +50,8 @@ def eval_board(dataset, eval_result0, eval_result1): # type: ignore # join evals together first joined_evals = varbar.add( "joined_evals", - weave.legacy.ops.join_all( - weave.legacy.ops.make_list( + weave.legacy.weave.ops.join_all( + weave.legacy.weave.ops.make_list( a=baseline_eval_result["eval_table"], b=candidate_eval_result["eval_table"], ), @@ -63,7 +63,7 @@ def eval_board(dataset, eval_result0, eval_result1): # type: ignore # then join dataset to evals dataset_evals = varbar.add( "dataset_evals", - weave.legacy.ops.join_2( + weave.legacy.weave.ops.join_2( dataset_var, joined_evals, lambda row: row["id"], @@ -75,7 +75,7 @@ def eval_board(dataset, eval_result0, eval_result1): # type: ignore ), ) - main = weave.legacy.panels.Group( + main = weave.legacy.weave.panels.Group( layoutMode="grid", showExpressions=True, enableAddPanel=True, @@ -87,72 +87,72 @@ def eval_board(dataset, eval_result0, eval_result1): # type: ignore main.add( "avg_f1", - weave.legacy.panels.Plot( + weave.legacy.weave.panels.Plot( summary, x=lambda row: row["avg_f1"], y=lambda row: row["name"], color=lambda row: row["name"], ), - layout=weave.legacy.panels.GroupPanelLayout(x=0, y=0, w=12, h=4), + layout=weave.legacy.weave.panels.GroupPanelLayout(x=0, y=0, w=12, h=4), ) main.add( "latency", - weave.legacy.panels.Plot( + weave.legacy.weave.panels.Plot( concatted_evals, x=lambda row: row["summary"]["latency"], y=lambda row: row["name"], color=lambda row: row["name"], mark="boxplot", ), - layout=weave.legacy.panels.GroupPanelLayout(x=12, y=0, w=12, h=4), + layout=weave.legacy.weave.panels.GroupPanelLayout(x=12, y=0, w=12, h=4), ) main.add( "field_name_f1", - weave.legacy.panels.Plot( + weave.legacy.weave.panels.Plot( summary, x=lambda row: row["field_name.f1"], y=lambda row: row["name"], color=lambda row: row["name"], ), - layout=weave.legacy.panels.GroupPanelLayout(x=0, y=4, w=8, h=4), + layout=weave.legacy.weave.panels.GroupPanelLayout(x=0, y=4, w=8, h=4), ) main.add( "field_shares_f1", - weave.legacy.panels.Plot( + weave.legacy.weave.panels.Plot( summary, x=lambda row: row["field_shares.f1"], y=lambda row: row["name"], color=lambda row: row["name"], ), - layout=weave.legacy.panels.GroupPanelLayout(x=8, y=4, w=8, h=4), + layout=weave.legacy.weave.panels.GroupPanelLayout(x=8, y=4, w=8, h=4), ) main.add( "field_directors_f1", - weave.legacy.panels.Plot( + weave.legacy.weave.panels.Plot( summary, x=lambda row: row["field_directors.f1"], y=lambda row: row["name"], color=lambda row: row["name"], ), - layout=weave.legacy.panels.GroupPanelLayout(x=16, y=4, w=8, h=4), + layout=weave.legacy.weave.panels.GroupPanelLayout(x=16, y=4, w=8, h=4), ) - # ct = main.add('concat_t', concatted_evals, layout=weave.legacy.panels.GroupPanelLayout(x=0, y=4, w=24, h=12)) + # ct = main.add('concat_t', concatted_evals, layout=weave.legacy.weave.panels.GroupPanelLayout(x=0, y=4, w=24, h=12)) # main.add('dataset_table', dataset) # main.add('joined_evals', joined_evals) # main.add( # "dataset_evals", # dataset_evals, - # layout=weave.legacy.panels.GroupPanelLayout(x=0, y=4, w=24, h=6), + # layout=weave.legacy.weave.panels.GroupPanelLayout(x=0, y=4, w=24, h=6), # ) ##### Example details # more ideas: show examples that all got wrong, or that are confusing - # facet_f1 = weave.legacy.panels.Facet( + # facet_f1 = weave.legacy.weave.panels.Facet( # dataset_evals, # x=lambda row: row["evals.summary"][0]["f1"], # y=lambda row: row["evals.summary"][1]["f1"], @@ -162,10 +162,10 @@ def eval_board(dataset, eval_result0, eval_result1): # type: ignore # f1_comparison = main.add( # "f1_comparison", # facet_f1, - # layout=weave.legacy.panels.GroupPanelLayout(x=0, y=8, w=12, h=6), + # layout=weave.legacy.weave.panels.GroupPanelLayout(x=0, y=8, w=12, h=6), # ) - facet_correct = weave.legacy.panels.Facet( + facet_correct = weave.legacy.weave.panels.Facet( dataset_evals, x=lambda row: row["evals.summary"][0]["correct"], x_title="baseline correct", @@ -177,34 +177,34 @@ def eval_board(dataset, eval_result0, eval_result1): # type: ignore correct_comparison = main.add( "correct_comparison", facet_correct, - layout=weave.legacy.panels.GroupPanelLayout(x=0, y=8, w=12, h=6), + layout=weave.legacy.weave.panels.GroupPanelLayout(x=0, y=8, w=12, h=6), ) main.add( "help", - weave.legacy.panels.PanelString( + weave.legacy.weave.panels.PanelString( "Click a cell in in the panel to the left to load examples for that cell.\n\nClick a row number in the table below to see details for that row.", mode="markdown", ), - layout=weave.legacy.panels.GroupPanelLayout(x=12, y=8, w=12, h=6), + layout=weave.legacy.weave.panels.GroupPanelLayout(x=12, y=8, w=12, h=6), ) # main.add( # "example_latencies", - # weave.legacy.panels.Plot( + # weave.legacy.weave.panels.Plot( # dataset_evals, # x=lambda row: row["evals.summary"]["latency"][0], # y=lambda row: row["evals.summary"]["latency"][1], # ), - # layout=weave.legacy.panels.GroupPanelLayout(x=12, y=8, w=12, h=6), + # layout=weave.legacy.weave.panels.GroupPanelLayout(x=12, y=8, w=12, h=6), # ) - sel_ex_table = weave.legacy.panels.Table(correct_comparison.selected()) + sel_ex_table = weave.legacy.weave.panels.Table(correct_comparison.selected()) sel_ex_table.config.rowSize = 2 sel_ex_table.add_column(lambda row: row["dataset.id"], "id") sel_ex_table.add_column(lambda row: row["dataset.example"], "example") sel_ex_table.add_column(lambda row: row["dataset.label.name"], "label.name") sel_ex_table.add_column( - lambda row: weave.legacy.ops.dict_( + lambda row: weave.legacy.weave.ops.dict_( baseline=row["evals.result"][0]["name"], candidate=row["evals.result"][1]["name"], ), @@ -212,7 +212,7 @@ def eval_board(dataset, eval_result0, eval_result1): # type: ignore ) sel_ex_table.add_column(lambda row: row["dataset.label.shares"], "label.shares") sel_ex_table.add_column( - lambda row: weave.legacy.ops.dict_( + lambda row: weave.legacy.weave.ops.dict_( baseilne=row["evals.result"][0]["shares"].toString(), candidate=row["evals.result"][1]["shares"].toString(), ), @@ -222,14 +222,14 @@ def eval_board(dataset, eval_result0, eval_result1): # type: ignore lambda row: row["dataset.label.directors"], "label.directors" ) sel_ex_table.add_column( - lambda row: weave.legacy.ops.dict_( + lambda row: weave.legacy.weave.ops.dict_( baseilne=row["evals.result"][0]["directors"].toString(), candidate=row["evals.result"][1]["directors"].toString(), ), "result.directors", ) sel_ex_table.add_column( - lambda row: weave.legacy.ops.dict_( + lambda row: weave.legacy.weave.ops.dict_( baseilne=row["evals.summary"][0]["latency"], candidate=row["evals.summary"][1]["latency"], ), @@ -239,19 +239,19 @@ def eval_board(dataset, eval_result0, eval_result1): # type: ignore selected_examples = main.add( "selected_examples", sel_ex_table, - layout=weave.legacy.panels.GroupPanelLayout(x=0, y=14, w=24, h=12), + layout=weave.legacy.weave.panels.GroupPanelLayout(x=0, y=14, w=24, h=12), ) main.add( "baseilne_detail", selected_examples.active_data()["evals.summary"][0], - layout=weave.legacy.panels.GroupPanelLayout(x=0, y=26, w=12, h=8), + layout=weave.legacy.weave.panels.GroupPanelLayout(x=0, y=26, w=12, h=8), ) main.add( "candidate_detail", selected_examples.active_data()["evals.summary"][1], - layout=weave.legacy.panels.GroupPanelLayout(x=12, y=26, w=12, h=8), + layout=weave.legacy.weave.panels.GroupPanelLayout(x=12, y=26, w=12, h=8), ) - return weave.legacy.panels.Board(vars=varbar, panels=main) + return weave.legacy.weave.panels.Board(vars=varbar, panels=main) diff --git a/weave/legacy/panels_py/panel_example_custom_board.py b/weave/legacy/weave/panels_py/panel_example_custom_board.py similarity index 76% rename from weave/legacy/panels_py/panel_example_custom_board.py rename to weave/legacy/weave/panels_py/panel_example_custom_board.py index 21ba4b37fdb..eb118a56d5d 100644 --- a/weave/legacy/panels_py/panel_example_custom_board.py +++ b/weave/legacy/weave/panels_py/panel_example_custom_board.py @@ -1,7 +1,7 @@ import weave -from weave import weave_internal as internal -from weave.legacy import weave_types as types -from weave.legacy.panels_py.generator_templates import template_registry +from weave.legacy.weave import weave_internal as internal +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.panels_py.generator_templates import template_registry # IMPORTANT: To enable this (or any template), it must be imported in # weave/panels_py/__init__.py This example is explicitly commented out since it @@ -40,13 +40,13 @@ ) def board( input_node, -) -> weave.legacy.panels.Group: +) -> weave.legacy.weave.panels.Group: # Define your VarBar variables here control_items = [ - weave.legacy.panels.GroupPanel( + weave.legacy.weave.panels.GroupPanel( internal.const("Example Custom Board"), id="title" ), - weave.legacy.panels.GroupPanel(input_node, id="data"), + weave.legacy.weave.panels.GroupPanel(input_node, id="data"), ] title_var = internal.make_var_node(input_node.type, "title") @@ -54,8 +54,8 @@ def board( # Define your panels here panels = [ - weave.legacy.panels.BoardPanel(title_var, id="title_panel"), - weave.legacy.panels.BoardPanel(data_var, id="table"), + weave.legacy.weave.panels.BoardPanel(title_var, id="title_panel"), + weave.legacy.weave.panels.BoardPanel(data_var, id="table"), ] # Example of using the input type to modify the board: @@ -63,13 +63,13 @@ def board( for column_name, column_type in input_node.type.object_type.property_types.items(): if types.optional(types.Number()).assign_type(column_type): panels.append( - weave.legacy.panels.BoardPanel( + weave.legacy.weave.panels.BoardPanel( data_var[column_name], # type: ignore id=column_name + "_distribution", ) ) - return weave.legacy.panels.Board(vars=control_items, panels=panels) + return weave.legacy.weave.panels.Board(vars=control_items, panels=panels) template_registry.register( diff --git a/weave/legacy/panels_py/panel_llm_monitor.py b/weave/legacy/weave/panels_py/panel_llm_monitor.py similarity index 85% rename from weave/legacy/panels_py/panel_llm_monitor.py rename to weave/legacy/weave/panels_py/panel_llm_monitor.py index a14e0e804d5..ed5c3563ec9 100644 --- a/weave/legacy/panels_py/panel_llm_monitor.py +++ b/weave/legacy/weave/panels_py/panel_llm_monitor.py @@ -2,16 +2,16 @@ import typing import weave -from weave import weave_internal -from weave import weave_internal as internal -from weave.legacy import weave_types as types -from weave.legacy import util, dispatch -from weave.legacy.panels import panel_board, panel_group -from weave.legacy.panels_py import panel_autoboard -from weave.legacy.panels_py.generator_templates import template_registry +from weave.legacy.weave import weave_internal +from weave.legacy.weave import weave_internal as internal +from weave.legacy.weave import weave_types as types +from weave.legacy.weave import util, dispatch +from weave.legacy.weave.panels import panel_board, panel_group +from weave.legacy.weave.panels_py import panel_autoboard +from weave.legacy.weave.panels_py.generator_templates import template_registry -panels = weave.legacy.panels -ops = weave.legacy.ops +panels = weave.legacy.weave.panels +ops = weave.legacy.weave.ops # BOARD_ID must be unique across all ops. It must only contain letters and underscores. @@ -101,7 +101,7 @@ # input_node: weave.Node[list[typing.Any]] # @weave.op() -# def render(self) -> weave.legacy.panels.Table: +# def render(self) -> weave.legacy.weave.panels.Table: # table = panels.Table( # self.input_node, # columns=[lambda row: row["inputs"]["messages"][-1]["content"]], @@ -137,7 +137,7 @@ def openai_request_cost(record) -> float: # type: ignore model = record["output.model"] pt = record["summary.prompt_tokens"] ct = record["summary.completion_tokens"] - cost_per_1000 = weave.legacy.ops.case( + cost_per_1000 = weave.legacy.weave.ops.case( [ # finetuned {"when": model.startsWith("ada:"), "then": pt * 0.0016 + ct * 0.0016}, @@ -196,7 +196,7 @@ def board( augmented_data = varbar.add( "augmented_data", source_data.with_columns( - weave.legacy.ops.dict_( + weave.legacy.weave.ops.dict_( **{ "summary.cost": source_data.map( lambda row: openai_request_cost(row) @@ -231,7 +231,7 @@ def board( ## 1. raw_data_range is derived from raw_data filtered_range = varbar.add( "filtered_range", - weave.legacy.ops.make_list( + weave.legacy.weave.ops.make_list( a=filtered_data[timestamp_col_name].min(), b=filtered_data[timestamp_col_name].max(), ), @@ -244,7 +244,7 @@ def board( ## 2.b: Setup a date picker to set the user_zoom_range varbar.add( "time_range", - weave.legacy.panels.DateRange( + weave.legacy.weave.panels.DateRange( user_zoom_range, domain=source_data[timestamp_col_name] ), ) @@ -259,7 +259,7 @@ def board( window_data = varbar.add( "window_data", augmented_data.filter( - lambda row: weave.legacy.ops.Boolean.bool_and( + lambda row: weave.legacy.weave.ops.Boolean.bool_and( row[timestamp_col_name] >= bin_range[0], row[timestamp_col_name] <= bin_range[1], ) @@ -269,7 +269,7 @@ def board( filters = varbar.add( "filters", - weave.legacy.panels.FilterEditor(filter_fn, node=window_data), + weave.legacy.weave.panels.FilterEditor(filter_fn, node=window_data), ) filtered_window_data = varbar.add( @@ -278,14 +278,14 @@ def board( grouping = varbar.add( "grouping", - weave.legacy.panels.GroupingEditor(grouping_fn, node=window_data), + weave.legacy.weave.panels.GroupingEditor(grouping_fn, node=window_data), ) height = 5 ### Overview tab - overview_tab = weave.legacy.panels.Group( + overview_tab = weave.legacy.weave.panels.Group( layoutMode="grid", showExpressions=True, enableAddPanel=True, @@ -304,7 +304,7 @@ def board( n_bins=100, mark="bar", ), - layout=weave.legacy.panels.GroupPanelLayout(x=0, y=0, w=24, h=height), + layout=weave.legacy.weave.panels.GroupPanelLayout(x=0, y=0, w=24, h=height), ) overview_tab.add( @@ -320,7 +320,7 @@ def board( x_domain=user_zoom_range, n_bins=50, ), - layout=weave.legacy.panels.GroupPanelLayout(x=0, y=height, w=12, h=height), + layout=weave.legacy.weave.panels.GroupPanelLayout(x=0, y=height, w=12, h=height), ) overview_tab.add( @@ -336,29 +336,29 @@ def board( x_domain=user_zoom_range, n_bins=50, ), - layout=weave.legacy.panels.GroupPanelLayout(x=12, y=height, w=12, h=height), + layout=weave.legacy.weave.panels.GroupPanelLayout(x=12, y=height, w=12, h=height), ) overview_tab.add( "avg_cost_per_req", filtered_window_data["summary.cost"].avg(), # type: ignore - layout=weave.legacy.panels.GroupPanelLayout(x=0, y=height * 2, w=6, h=3), + layout=weave.legacy.weave.panels.GroupPanelLayout(x=0, y=height * 2, w=6, h=3), ) overview_tab.add( "avg_prompt_tokens_per_req", filtered_window_data["summary.prompt_tokens"].avg(), # type: ignore - layout=weave.legacy.panels.GroupPanelLayout(x=6, y=height * 2, w=6, h=3), + layout=weave.legacy.weave.panels.GroupPanelLayout(x=6, y=height * 2, w=6, h=3), ) overview_tab.add( "avg_completion_tokens_per_req", filtered_window_data["summary.completion_tokens"].avg(), # type: ignore - layout=weave.legacy.panels.GroupPanelLayout(x=12, y=height * 2, w=6, h=3), + layout=weave.legacy.weave.panels.GroupPanelLayout(x=12, y=height * 2, w=6, h=3), ) ( overview_tab.add( "avg_total_tokens_per_req", filtered_window_data["summary.total_tokens"].avg(), # type: ignore - layout=weave.legacy.panels.GroupPanelLayout(x=18, y=height * 2, w=6, h=3), + layout=weave.legacy.weave.panels.GroupPanelLayout(x=18, y=height * 2, w=6, h=3), ), ) @@ -366,12 +366,12 @@ def board( # TODO: This doesn't really work yet (needs some manual UI configuration currently, # and it's ugly). # overview_tab.add( - # "attributes", weave.legacy.panels.EachColumn(filtered_window_data["attributes"]) + # "attributes", weave.legacy.weave.panels.EachColumn(filtered_window_data["attributes"]) # ) ### Requests tab - # requests_tab = weave.legacy.panels.Group( + # requests_tab = weave.legacy.weave.panels.Group( # layoutMode="grid", # showExpressions=True, # ) # l, showExpressions="titleBar") @@ -399,7 +399,7 @@ def board( requests_table_var = overview_tab.add( "table", requests_table, - layout=weave.legacy.panels.GroupPanelLayout(x=0, y=13, w=24, h=8), + layout=weave.legacy.weave.panels.GroupPanelLayout(x=0, y=13, w=24, h=8), ) overview_tab.add( "input", @@ -407,19 +407,19 @@ def board( requests_table_var.active_data()["inputs.messages"], columns=[lambda row: row["role"], lambda row: row["content"]], ), - layout=weave.legacy.panels.GroupPanelLayout(x=0, y=21, w=12, h=8), + layout=weave.legacy.weave.panels.GroupPanelLayout(x=0, y=21, w=12, h=8), ) overview_tab.add( "output", requests_table_var.active_row(), - layout=weave.legacy.panels.GroupPanelLayout(x=12, y=21, w=12, h=8), + layout=weave.legacy.weave.panels.GroupPanelLayout(x=12, y=21, w=12, h=8), ) - # attributes_tab = weave.legacy.panels.Group(layoutMode="grid") + # attributes_tab = weave.legacy.weave.panels.Group(layoutMode="grid") - # users_tab = weave.legacy.panels.Group(layoutMode="grid") + # users_tab = weave.legacy.weave.panels.Group(layoutMode="grid") - # models_tab = weave.legacy.panels.Group(layoutMode="grid") + # models_tab = weave.legacy.weave.panels.Group(layoutMode="grid") # tabs = panels.Group( # layoutMode="tab", diff --git a/weave/legacy/panels_py/panel_observability.py b/weave/legacy/weave/panels_py/panel_observability.py similarity index 81% rename from weave/legacy/panels_py/panel_observability.py rename to weave/legacy/weave/panels_py/panel_observability.py index 37a3d47d6bc..1987039cfcb 100644 --- a/weave/legacy/panels_py/panel_observability.py +++ b/weave/legacy/weave/panels_py/panel_observability.py @@ -1,10 +1,10 @@ import weave -from weave import weave_internal -from weave.legacy import weave_types as types -from weave.legacy.panels import panel_board -from weave.legacy.panels_py.generator_templates import template_registry +from weave.legacy.weave import weave_internal +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.panels import panel_board +from weave.legacy.weave.panels_py.generator_templates import template_registry -panels = weave.legacy.panels +panels = weave.legacy.weave.panels BOARD_INPUT_WEAVE_TYPE = types.List( types.TypedDict( @@ -76,16 +76,16 @@ def observability( timestamp_col_name = "timestamp" num_buckets = 80 - now = weave.legacy.ops.datetime_now() + now = weave.legacy.weave.ops.datetime_now() - dashboard = weave.legacy.panels.Group( + dashboard = weave.legacy.weave.panels.Group( layoutMode="grid", showExpressions=False, enableAddPanel=False, disableDeletePanel=True, ) - display_states = weave.legacy.ops.dict_( + display_states = weave.legacy.weave.ops.dict_( **{ "running": "running", "failed": "failed", @@ -100,7 +100,7 @@ def observability( varbar = panel_board.varbar(editable=False) source_data = input_node.map( - lambda row: weave.legacy.ops.dict_( + lambda row: weave.legacy.weave.ops.dict_( **{ "timestamp": row[timestamp_col_name], "entity_name": row["entity_name"], @@ -141,18 +141,18 @@ def observability( seven_days_in_seconds = 60 * 60 * 24 * 7 window_start = varbar.add( "window_start", - weave.legacy.ops.from_number(now - seven_days_in_seconds), + weave.legacy.weave.ops.from_number(now - seven_days_in_seconds), hidden=True, ) window_end = varbar.add( "window_end", - weave.legacy.ops.from_number(now), + weave.legacy.weave.ops.from_number(now), hidden=True, ) filtered_range = varbar.add( "filtered_range", - weave.legacy.ops.make_list( + weave.legacy.weave.ops.make_list( a=window_start, b=window_end, ), @@ -173,9 +173,9 @@ def observability( ) window_data = source_data.filter( - lambda row: weave.legacy.ops.Boolean.bool_and( + lambda row: weave.legacy.weave.ops.Boolean.bool_and( row["state"] != "TO_REMOVE", # remove hidden states - weave.legacy.ops.Boolean.bool_and( + weave.legacy.weave.ops.Boolean.bool_and( row[timestamp_col_name] >= bin_range[0], row[timestamp_col_name] <= bin_range[1], ), @@ -196,7 +196,7 @@ def observability( panels.FilterEditor(filter_fn, node=window_data), ) - colors_node = weave.legacy.ops.dict_( + colors_node = weave.legacy.weave.ops.dict_( **{ "running": "rgb(57, 126, 237)", "failed": "rgb(255, 122, 136)", @@ -216,7 +216,7 @@ def observability( queued_time_data = varbar.add( "queued_time_data", filtered_window_data.filter( - lambda row: weave.legacy.ops.Boolean.bool_or( + lambda row: weave.legacy.weave.ops.Boolean.bool_or( row["state"] == "queued", row["state"] == "pending", ), @@ -226,11 +226,11 @@ def observability( is_start_stop_state = weave_internal.define_fn( {"row": source_data.type.object_type}, - lambda row: weave.legacy.ops.Boolean.bool_or( + lambda row: weave.legacy.weave.ops.Boolean.bool_or( row["state"] == "running", - weave.legacy.ops.Boolean.bool_or( + weave.legacy.weave.ops.Boolean.bool_or( row["state"] == "finished", - weave.legacy.ops.Boolean.bool_or( + weave.legacy.weave.ops.Boolean.bool_or( row["state"] == "crashed", row["state"] == "failed", ), @@ -241,17 +241,17 @@ def observability( state_transitions_plot = panels.Plot( filtered_window_data, x=lambda row: row[timestamp_col_name].bin( - weave.legacy.ops.timestamp_bins_nice(bin_range, num_buckets) + weave.legacy.weave.ops.timestamp_bins_nice(bin_range, num_buckets) ), x_title="Time", y=lambda row: row.count(), y_title="Count of transitions by state", label=lambda row: row["state"], - tooltip=lambda row: weave.legacy.ops.dict_( + tooltip=lambda row: weave.legacy.weave.ops.dict_( **{ "State": row["state"][0], "Count": row.count(), - "Job (s)": weave.legacy.ops.join_to_str(row["job"].unique(), ","), + "Job (s)": weave.legacy.weave.ops.join_to_str(row["job"].unique(), ","), } ), color_title="state", @@ -261,12 +261,12 @@ def observability( domain_x=user_zoom_range, ) - runs_grouped = weave.legacy.ops.List.groupby( + runs_grouped = weave.legacy.weave.ops.List.groupby( queued_time_data, lambda row: row["trace_id"] ) - runs_mapped = weave.legacy.ops.List.map( + runs_mapped = weave.legacy.weave.ops.List.map( runs_grouped, - lambda row: weave.legacy.ops.dict_( + lambda row: weave.legacy.weave.ops.dict_( **{ "trace_id": row["trace_id"][0], "entity_name": row["entity_name"][0], @@ -275,10 +275,10 @@ def observability( "job": row["job"][-1], "enqueued": row[timestamp_col_name][0], "run_start": row[timestamp_col_name][-1], - "duration": weave.legacy.ops.Number.__truediv__( - weave.legacy.ops.Number.__mul__( - weave.legacy.ops.timedelta_total_seconds( - weave.legacy.ops.datetime_sub( + "duration": weave.legacy.weave.ops.Number.__truediv__( + weave.legacy.weave.ops.Number.__mul__( + weave.legacy.weave.ops.timedelta_total_seconds( + weave.legacy.weave.ops.datetime_sub( row[timestamp_col_name].max(), row[timestamp_col_name].min(), ), @@ -292,7 +292,7 @@ def observability( ) runs_mapped_filtered = varbar.add( "runs_mapped_filtered", - weave.legacy.ops.List.filter( + weave.legacy.weave.ops.List.filter( runs_mapped, lambda row: row["duration"] >= 0, ), @@ -302,19 +302,19 @@ def observability( queued_time_plot = panels.Plot( runs_mapped_filtered, x=lambda row: row["run_start"].bin( - weave.legacy.ops.timestamp_bins_nice(bin_range, num_buckets) + weave.legacy.weave.ops.timestamp_bins_nice(bin_range, num_buckets) ), x_title="Time", y=lambda row: row["duration"].sum(), y_title="Time spent queued (m)", label=lambda row: grouping_fn(row), - tooltip=lambda row: weave.legacy.ops.dict_( + tooltip=lambda row: weave.legacy.weave.ops.dict_( **{ - "Job (s)": weave.legacy.ops.join_to_str(row["job"].unique(), ","), - "User (s)": weave.legacy.ops.join_to_str( + "Job (s)": weave.legacy.weave.ops.join_to_str(row["job"].unique(), ","), + "User (s)": weave.legacy.weave.ops.join_to_str( row["entity_name"].unique(), "," ), - "Project (s)": weave.legacy.ops.join_to_str( + "Project (s)": weave.legacy.weave.ops.join_to_str( row["project_name"].unique(), "," ), "Duration (m)": row["duration"].sum(), @@ -329,7 +329,7 @@ def observability( start_stop_states = varbar.add( "start_stop_states", - weave.legacy.ops.List.filter(filtered_window_data, is_start_stop_state), + weave.legacy.weave.ops.List.filter(filtered_window_data, is_start_stop_state), hidden=True, ) @@ -339,7 +339,7 @@ def observability( x_title="Time", y_title="Job", y=lambda row: row["job"], - tooltip=lambda row: weave.legacy.ops.dict_( + tooltip=lambda row: weave.legacy.weave.ops.dict_( **{ "Job": row["job"][0], "User": row["entity_name"][0], @@ -347,10 +347,10 @@ def observability( "Project": row["project_name"][0], "Run ID": row["run_id"][-1], "Status": row["state"][-1], - "Duration (m)": weave.legacy.ops.Number.__truediv__( - weave.legacy.ops.Number.__mul__( - weave.legacy.ops.timedelta_total_seconds( - weave.legacy.ops.datetime_sub( + "Duration (m)": weave.legacy.weave.ops.Number.__truediv__( + weave.legacy.weave.ops.Number.__mul__( + weave.legacy.weave.ops.timedelta_total_seconds( + weave.legacy.weave.ops.datetime_sub( row[timestamp_col_name].max(), row[timestamp_col_name].min(), ), @@ -378,7 +378,7 @@ def observability( jobs_table = panels.Table(jobs) # type: ignore jobs_table.add_column(lambda row: row["run_id"], "Run", groupby=True) jobs_table.add_column( - lambda row: weave.legacy.ops.run_ops.str_run_link( + lambda row: weave.legacy.weave.ops.run_ops.str_run_link( entity_name=row["queue_uri"][0].split(":").index(2), project_name=row["project_name"][0], name=row["run_id"][0], @@ -396,26 +396,26 @@ def observability( "Current state", ) jobs_table.add_column( - lambda row: weave.legacy.ops.cond( - weave.legacy.ops.dict_( + lambda row: weave.legacy.weave.ops.cond( + weave.legacy.weave.ops.dict_( a=row["state"][-1] != "running", b=row["state"][-1] == "running", ), - weave.legacy.ops.dict_( - a=weave.legacy.ops.Number.__truediv__( - weave.legacy.ops.timedelta_total_seconds( - weave.legacy.ops.datetime_sub( + weave.legacy.weave.ops.dict_( + a=weave.legacy.weave.ops.Number.__truediv__( + weave.legacy.weave.ops.timedelta_total_seconds( + weave.legacy.weave.ops.datetime_sub( row[timestamp_col_name].max(), row[timestamp_col_name].min(), ) ), 60, ), - b=weave.legacy.ops.Number.__truediv__( - weave.legacy.ops.timedelta_total_seconds( - weave.legacy.ops.datetime_sub( - weave.legacy.ops.from_number( - weave.legacy.ops.datetime_now() + b=weave.legacy.weave.ops.Number.__truediv__( + weave.legacy.weave.ops.timedelta_total_seconds( + weave.legacy.weave.ops.datetime_sub( + weave.legacy.weave.ops.from_number( + weave.legacy.weave.ops.datetime_now() ), row[timestamp_col_name].min(), ) @@ -427,66 +427,66 @@ def observability( "Runtime (m)", ) jobs_table.add_column( - lambda row: weave.legacy.ops.cond( - weave.legacy.ops.dict_( + lambda row: weave.legacy.weave.ops.cond( + weave.legacy.weave.ops.dict_( a=row["state"][-1] != "running", b=row["state"][-1] == "running", ), - weave.legacy.ops.dict_( + weave.legacy.weave.ops.dict_( a=row["metrics"]["system"]["cpu_cores_util"][-1].avg(), - b=weave.legacy.ops.make_const_node(types.NoneType(), None), + b=weave.legacy.weave.ops.make_const_node(types.NoneType(), None), ), ), "Avg. CPU %", ) jobs_table.add_column( - lambda row: weave.legacy.ops.cond( - weave.legacy.ops.dict_( + lambda row: weave.legacy.weave.ops.cond( + weave.legacy.weave.ops.dict_( a=row["state"][-1] != "running", b=row["state"][-1] == "running", ), - weave.legacy.ops.dict_( + weave.legacy.weave.ops.dict_( a=row["metrics"]["system"]["gpu_cores_util"][-1].avg(), - b=weave.legacy.ops.make_const_node(types.NoneType(), None), + b=weave.legacy.weave.ops.make_const_node(types.NoneType(), None), ), ), "Avg. GPU %", ) jobs_table.add_column( - lambda row: weave.legacy.ops.cond( - weave.legacy.ops.dict_( + lambda row: weave.legacy.weave.ops.cond( + weave.legacy.weave.ops.dict_( a=row["state"][-1] != "running", b=row["state"][-1] == "running", ), - weave.legacy.ops.dict_( + weave.legacy.weave.ops.dict_( a=row["metrics"]["system"]["gpu_cores_mem"][-1].avg(), - b=weave.legacy.ops.make_const_node(types.NoneType(), None), + b=weave.legacy.weave.ops.make_const_node(types.NoneType(), None), ), ), "Avg. GPU mem. %", ) jobs_table.add_column( - lambda row: weave.legacy.ops.cond( - weave.legacy.ops.dict_( + lambda row: weave.legacy.weave.ops.cond( + weave.legacy.weave.ops.dict_( a=row["state"][-1] != "running", b=row["state"][-1] == "running", ), - weave.legacy.ops.dict_( + weave.legacy.weave.ops.dict_( a=row["metrics"]["system"]["memory"][-1], - b=weave.legacy.ops.make_const_node(types.NoneType(), None), + b=weave.legacy.weave.ops.make_const_node(types.NoneType(), None), ), ), "Avg. mem (MB)", ) - runs_table_data = weave.legacy.ops.List.groupby( + runs_table_data = weave.legacy.weave.ops.List.groupby( filtered_window_data, lambda row: row["trace_id"] ) runs_table_data_mapped = varbar.add( "runs_table_data", - weave.legacy.ops.List.map( + weave.legacy.weave.ops.List.map( runs_table_data, - lambda row: weave.legacy.ops.dict_( + lambda row: weave.legacy.weave.ops.dict_( **{ "trace_id": row["trace_id"][0], "user": row["entity_name"][0], @@ -522,7 +522,7 @@ def observability( ) runs_table.add_column( lambda row: row.filter( - lambda row: weave.legacy.ops.Boolean.bool_or( + lambda row: weave.legacy.weave.ops.Boolean.bool_or( row["states"][-1] == "crashed", row["states"][-1] == "failed", ), @@ -533,9 +533,9 @@ def observability( finished_runs = varbar.add( "finished_runs", filtered_window_data.filter( - lambda row: weave.legacy.ops.Boolean.bool_or( + lambda row: weave.legacy.weave.ops.Boolean.bool_or( row["state"] == "finished", - weave.legacy.ops.Boolean.bool_or( + weave.legacy.weave.ops.Boolean.bool_or( row["state"] == "crashed", row["state"] == "failed", ), @@ -556,24 +556,24 @@ def observability( no_legend=True, ) - metric_plot_data = weave.legacy.ops.List.groupby( + metric_plot_data = weave.legacy.weave.ops.List.groupby( start_stop_states, lambda row: row["trace_id"] ) metric_plot_data_mapped = varbar.add( "metric_plot_data", - weave.legacy.ops.List.map( + weave.legacy.weave.ops.List.map( metric_plot_data, - lambda row: weave.legacy.ops.dict_( + lambda row: weave.legacy.weave.ops.dict_( **{ "trace_id": row["trace_id"][0], "run_id": row["run_id"][-1], "entity_name": row["entity_name"][0], "project_name": row["project_name"][0], "job": row["job"][0], - "duration": weave.legacy.ops.Number.__truediv__( - weave.legacy.ops.Number.__mul__( - weave.legacy.ops.timedelta_total_seconds( - weave.legacy.ops.datetime_sub( + "duration": weave.legacy.weave.ops.Number.__truediv__( + weave.legacy.weave.ops.Number.__mul__( + weave.legacy.weave.ops.timedelta_total_seconds( + weave.legacy.weave.ops.datetime_sub( row[timestamp_col_name].max(), row[timestamp_col_name].min(), ), @@ -598,7 +598,7 @@ def observability( x_title="Run duration (minutes)", y_title="GPU utilization (%)", y=lambda row: row["GPU util %"], - tooltip=lambda row: weave.legacy.ops.dict_( + tooltip=lambda row: weave.legacy.weave.ops.dict_( **{ "Run ID": row["run_id"], "Project": row["project_name"], @@ -622,7 +622,7 @@ def observability( x_title="Run duration (minutes)", y_title="CPU utilization (%)", y=lambda row: row["CPU util %"], - tooltip=lambda row: weave.legacy.ops.dict_( + tooltip=lambda row: weave.legacy.weave.ops.dict_( **{ "Run ID": row["run_id"], "Project": row["project_name"], @@ -646,7 +646,7 @@ def observability( x_title="Run duration (minutes)", y_title="GPU memory (%)", y=lambda row: row["GPU memory %"], - tooltip=lambda row: weave.legacy.ops.dict_( + tooltip=lambda row: weave.legacy.weave.ops.dict_( **{ "Run ID": row["run_id"], "Project": row["project_name"], @@ -670,7 +670,7 @@ def observability( x_title="Run duration (minutes)", y_title="Memory usage (MB)", y=lambda row: row["Memory (MB)"], - tooltip=lambda row: weave.legacy.ops.dict_( + tooltip=lambda row: weave.legacy.weave.ops.dict_( **{ "Run ID": row["run_id"], "Project": row["project_name"], @@ -724,7 +724,7 @@ def observability( ) selected_jobs = panels.Table(latest_runs_plot_selector.selected_rows()) # type: ignore selected_jobs.add_column( - lambda row: weave.legacy.ops.run_ops.str_run_link( + lambda row: weave.legacy.weave.ops.run_ops.str_run_link( entity_name=row["c_4.Team"], project_name=row["c_4.Project"], name=row["c_4.Run ID"], diff --git a/weave/legacy/panels_py/panel_seedboard.py b/weave/legacy/weave/panels_py/panel_seedboard.py similarity index 60% rename from weave/legacy/panels_py/panel_seedboard.py rename to weave/legacy/weave/panels_py/panel_seedboard.py index a8ffbc5b2c1..c21cd0e66de 100644 --- a/weave/legacy/panels_py/panel_seedboard.py +++ b/weave/legacy/weave/panels_py/panel_seedboard.py @@ -1,8 +1,8 @@ import typing import weave -from weave import weave_internal -from weave.legacy.panels_py.generator_templates import template_registry +from weave.legacy.weave import weave_internal +from weave.legacy.weave.panels_py.generator_templates import template_registry @weave.type() @@ -17,22 +17,22 @@ class PyBoardSeedBoardConfig: def seed_board( input_node: weave.Node[typing.Any], config: typing.Optional[PyBoardSeedBoardConfig] = None, -) -> weave.legacy.panels.Group: +) -> weave.legacy.weave.panels.Group: control_items = [ - weave.legacy.panels.GroupPanel( + weave.legacy.weave.panels.GroupPanel( input_node, id="data", ), ] panels = [ - weave.legacy.panels.BoardPanel( + weave.legacy.weave.panels.BoardPanel( weave_internal.make_var_node(input_node.type, "data"), id="panel", - layout=weave.legacy.panels.BoardPanelLayout(x=0, y=0, w=24, h=6), + layout=weave.legacy.weave.panels.BoardPanelLayout(x=0, y=0, w=24, h=6), ), ] - return weave.legacy.panels.Board(vars=control_items, panels=panels) + return weave.legacy.weave.panels.Board(vars=control_items, panels=panels) template_registry.register( diff --git a/weave/legacy/panels_py/panel_trace_monitor.py b/weave/legacy/weave/panels_py/panel_trace_monitor.py similarity index 81% rename from weave/legacy/panels_py/panel_trace_monitor.py rename to weave/legacy/weave/panels_py/panel_trace_monitor.py index 7e228aeef01..e1cf168c432 100644 --- a/weave/legacy/panels_py/panel_trace_monitor.py +++ b/weave/legacy/weave/panels_py/panel_trace_monitor.py @@ -1,17 +1,17 @@ import os import weave -from weave import weave_internal -from weave import weave_internal as internal -from weave.legacy import weave_types as types -from weave.legacy import util, dispatch, graph -from weave.legacy.panels import panel_board, panel_group, panel_trace -from weave.legacy.panels.panel_trace_span import TraceSpanModelPanel, TraceSpanPanel -from weave.legacy.panels_py import panel_autoboard -from weave.legacy.panels_py.generator_templates import template_registry +from weave.legacy.weave import weave_internal +from weave.legacy.weave import weave_internal as internal +from weave.legacy.weave import weave_types as types +from weave.legacy.weave import util, dispatch, graph +from weave.legacy.weave.panels import panel_board, panel_group, panel_trace +from weave.legacy.weave.panels.panel_trace_span import TraceSpanModelPanel, TraceSpanPanel +from weave.legacy.weave.panels_py import panel_autoboard +from weave.legacy.weave.panels_py.generator_templates import template_registry -panels = weave.legacy.panels -ops = weave.legacy.ops +panels = weave.legacy.weave.panels +ops = weave.legacy.weave.ops # BOARD_ID must be unique across all ops. It must only contain letters and underscores. @@ -101,7 +101,7 @@ def board( ## 1. raw_data_range is derived from raw_data filtered_range = varbar.add( "filtered_range", - weave.legacy.ops.make_list( + weave.legacy.weave.ops.make_list( a=filtered_data[timestamp_col_name].min(), b=filtered_data[timestamp_col_name].max(), ), @@ -114,7 +114,7 @@ def board( ## 2.b: Setup a date picker to set the user_zoom_range varbar.add( "time_range", - weave.legacy.panels.DateRange( + weave.legacy.weave.panels.DateRange( user_zoom_range, domain=trace_roots[timestamp_col_name] ), ) @@ -129,7 +129,7 @@ def board( window_data = varbar.add( "window_data", trace_roots.filter( - lambda row: weave.legacy.ops.Boolean.bool_and( + lambda row: weave.legacy.weave.ops.Boolean.bool_and( row[timestamp_col_name] >= bin_range[0], row[timestamp_col_name] <= bin_range[1], ) @@ -139,7 +139,7 @@ def board( filters = varbar.add( "filters", - weave.legacy.panels.FilterEditor(filter_fn, node=window_data), + weave.legacy.weave.panels.FilterEditor(filter_fn, node=window_data), ) filtered_window_data = varbar.add( @@ -148,7 +148,7 @@ def board( ### Overview tab - overview_tab = weave.legacy.panels.Group( + overview_tab = weave.legacy.weave.panels.Group( layoutMode="grid", showExpressions=True, enableAddPanel=True, @@ -171,13 +171,13 @@ def board( x_domain=user_zoom_range, n_bins=50, ), - layout=weave.legacy.panels.GroupPanelLayout(x=0, y=0, w=6, h=6), + layout=weave.legacy.weave.panels.GroupPanelLayout(x=0, y=0, w=6, h=6), ) overview_tab.add( "latency_distribution", filtered_window_data.map(lambda row: row["end_time_s"] - row["start_time_s"]), - layout=weave.legacy.panels.GroupPanelLayout(x=6, y=0, w=6, h=6), + layout=weave.legacy.weave.panels.GroupPanelLayout(x=6, y=0, w=6, h=6), ) overview_tab.add( @@ -198,12 +198,12 @@ def board( x_domain=user_zoom_range, n_bins=50, ), - layout=weave.legacy.panels.GroupPanelLayout(x=12, y=0, w=6, h=6), + layout=weave.legacy.weave.panels.GroupPanelLayout(x=12, y=0, w=6, h=6), ) overview_tab.add( "success_distribution", - weave.legacy.ops.dict_( + weave.legacy.weave.ops.dict_( **{ "success": filtered_window_data.filter( lambda row: row["status_code"] == "SUCCESS" @@ -213,13 +213,13 @@ def board( ).count(), } ), - layout=weave.legacy.panels.GroupPanelLayout(x=18, y=0, w=6, h=6), + layout=weave.legacy.weave.panels.GroupPanelLayout(x=18, y=0, w=6, h=6), ) traces_table_var = overview_tab.add( "traces_table", make_span_table(filtered_window_data), - layout=weave.legacy.panels.GroupPanelLayout(x=0, y=6, w=24, h=6), + layout=weave.legacy.weave.panels.GroupPanelLayout(x=0, y=6, w=24, h=6), ) trace_spans = all_spans.filter( @@ -231,13 +231,13 @@ def board( trace_viewer_var = overview_tab.add( "trace_viewer", trace_viewer, - layout=weave.legacy.panels.GroupPanelLayout(x=0, y=12, w=16, h=6), + layout=weave.legacy.weave.panels.GroupPanelLayout(x=0, y=12, w=16, h=6), ) selected_trace_model = overview_tab.add( "selected_trace_model", TraceSpanModelPanel(traces_table_var.active_data()), - layout=weave.legacy.panels.GroupPanelLayout(x=0, y=18, w=16, h=6), + layout=weave.legacy.weave.panels.GroupPanelLayout(x=0, y=18, w=16, h=6), ) active_span = trace_viewer_var.active_span() @@ -245,7 +245,7 @@ def board( selected_span_details = overview_tab.add( "selected_span_details", TraceSpanPanel(active_span), - layout=weave.legacy.panels.GroupPanelLayout(x=16, y=12, w=8, h=12), + layout=weave.legacy.weave.panels.GroupPanelLayout(x=16, y=12, w=8, h=12), ) similar_spans = all_spans.filter(lambda row: row["name"] == active_span["name"]) @@ -254,7 +254,7 @@ def board( similar_spans_table_var = overview_tab.add( "similar_spans_table", similar_spans_table, - layout=weave.legacy.panels.GroupPanelLayout(x=0, y=22, w=24, h=6), + layout=weave.legacy.weave.panels.GroupPanelLayout(x=0, y=22, w=24, h=6), ) return panels.Board(vars=varbar, panels=overview_tab) diff --git a/weave/legacy/parallelism.py b/weave/legacy/weave/parallelism.py similarity index 99% rename from weave/legacy/parallelism.py rename to weave/legacy/weave/parallelism.py index a11dcfeac76..bd27582d836 100644 --- a/weave/legacy/parallelism.py +++ b/weave/legacy/weave/parallelism.py @@ -3,7 +3,7 @@ from concurrent.futures import ThreadPoolExecutor from typing import Callable, Generator, Iterator, Optional, TypeVar -from weave.legacy import ( +from weave.legacy.weave import ( cache, context, context_state, diff --git a/weave/legacy/partial_object.py b/weave/legacy/weave/partial_object.py similarity index 96% rename from weave/legacy/partial_object.py rename to weave/legacy/weave/partial_object.py index 306c12a2d73..bec6862b70a 100644 --- a/weave/legacy/partial_object.py +++ b/weave/legacy/weave/partial_object.py @@ -2,8 +2,8 @@ import typing from dataclasses import dataclass, field -from weave.legacy import weave_types as types -from weave.legacy import artifact_fs +from weave.legacy.weave import weave_types as types +from weave.legacy.weave import artifact_fs T = typing.TypeVar("T", bound="PartialObject") @@ -136,7 +136,7 @@ def from_dict(cls, d: dict) -> "PartialObjectType": def save_instance( self, obj: PartialObject, artifact: artifact_fs.FilesystemArtifact, name: str ) -> None: - from weave.legacy import mappers_python + from weave.legacy.weave import mappers_python serializer = mappers_python.map_to_python(self, artifact) result = serializer.apply(obj) @@ -151,7 +151,7 @@ def load_instance( name: str, extra: typing.Optional[list] = None, ) -> PartialObject: - from weave.legacy import mappers_python + from weave.legacy.weave import mappers_python with artifact.open( f"{name}.{self.keyless_weave_type_class.__name__}WithKeys.json" diff --git a/weave/legacy/path_util.py b/weave/legacy/weave/path_util.py similarity index 90% rename from weave/legacy/path_util.py rename to weave/legacy/weave/path_util.py index a6504697c37..e52d5fa912d 100644 --- a/weave/legacy/path_util.py +++ b/weave/legacy/weave/path_util.py @@ -1,7 +1,7 @@ import pathlib import typing -from weave.legacy import errors +from weave.legacy.weave import errors def safe_join(*args: typing.Union[str, pathlib.Path]) -> str: diff --git a/weave/legacy/propagate_gql_keys.py b/weave/legacy/weave/propagate_gql_keys.py similarity index 93% rename from weave/legacy/propagate_gql_keys.py rename to weave/legacy/weave/propagate_gql_keys.py index b32fbd3ad32..a605b407cf3 100644 --- a/weave/legacy/propagate_gql_keys.py +++ b/weave/legacy/weave/propagate_gql_keys.py @@ -1,8 +1,8 @@ import typing -from weave.legacy import weave_types as types -from weave.legacy import gql_op_plugin, graph, input_provider, op_def, partial_object, registry_mem +from weave.legacy.weave import weave_types as types +from weave.legacy.weave import gql_op_plugin, graph, input_provider, op_def, partial_object, registry_mem def _propagate_gql_keys_for_node( @@ -13,7 +13,7 @@ def _propagate_gql_keys_for_node( ) -> types.Type: # Mutates node # TODO: see if this can be done without mutations - from weave.legacy.language_features.tagging import ( + from weave.legacy.weave.language_features.tagging import ( opdef_util, tagged_value_type, tagged_value_type_helpers, diff --git a/weave/legacy/pyfunc_type_util.py b/weave/legacy/weave/pyfunc_type_util.py similarity index 98% rename from weave/legacy/pyfunc_type_util.py rename to weave/legacy/weave/pyfunc_type_util.py index c72318e515d..2a12827822f 100644 --- a/weave/legacy/pyfunc_type_util.py +++ b/weave/legacy/weave/pyfunc_type_util.py @@ -1,10 +1,10 @@ import inspect import typing -from weave.legacy import infer_types, op_args +from weave.legacy.weave import infer_types, op_args -from weave.legacy import errors -from weave.legacy import weave_types as types +from weave.legacy.weave import errors +from weave.legacy.weave import weave_types as types InputTypeItemType = typing.Union[types.Type, typing.Callable[..., types.Type]] InputTypeType = typing.Union[op_args.OpArgs, typing.Mapping[str, InputTypeItemType]] diff --git a/weave/legacy/ref_base.py b/weave/legacy/weave/ref_base.py similarity index 94% rename from weave/legacy/ref_base.py rename to weave/legacy/weave/ref_base.py index 234b9f4aede..6951fe602b3 100644 --- a/weave/legacy/ref_base.py +++ b/weave/legacy/weave/ref_base.py @@ -5,18 +5,18 @@ import weakref from typing import Sequence -from weave.client_context import weave_client as weave_client_context -from weave.legacy import box, context_state, object_context, uris -from weave.legacy.language_features.tagging import tag_store +from weave.trace.client_context import weave_client as weave_client_context +from weave.legacy.weave import box, context_state, object_context, uris +from weave.legacy.weave.language_features.tagging import tag_store -from weave.legacy import errors -from weave.legacy import weave_types as types +from weave.legacy.weave import errors +from weave.legacy.weave import weave_types as types # We store Refs here if we can't attach them directly to the object REFS: weakref.WeakValueDictionary[int, "Ref"] = weakref.WeakValueDictionary() if typing.TYPE_CHECKING: - from weave.legacy import weave_types as types + from weave.legacy.weave import weave_types as types from ..trace import weave_client diff --git a/weave/legacy/registry_mem.py b/weave/legacy/weave/registry_mem.py similarity index 95% rename from weave/legacy/registry_mem.py rename to weave/legacy/weave/registry_mem.py index 625c18ca60e..7e0f0905b17 100644 --- a/weave/legacy/registry_mem.py +++ b/weave/legacy/weave/registry_mem.py @@ -1,12 +1,12 @@ import datetime import typing -from weave.legacy import weave_types -from weave.legacy import storage, errors, context_state, op_aliases, op_args, uris -from weave.legacy.op_args import OpNamedArgs +from weave.legacy.weave import weave_types +from weave.legacy.weave import storage, errors, context_state, op_aliases, op_args, uris +from weave.legacy.weave.op_args import OpNamedArgs if typing.TYPE_CHECKING: - from weave.legacy.op_def import OpDef + from weave.legacy.weave.op_def import OpDef class Registry: @@ -122,7 +122,7 @@ def is_chainable(op): # type: ignore return [op for op in self._ops.values() if is_chainable(op)] # type: ignore[no-untyped-call] def load_saved_ops(self): # type: ignore - from weave.legacy import op_def_type + from weave.legacy.weave import op_def_type for op_ref in storage.objects(op_def_type.OpDefType()): try: diff --git a/weave/legacy/run.py b/weave/legacy/weave/run.py similarity index 100% rename from weave/legacy/run.py rename to weave/legacy/weave/run.py diff --git a/weave/legacy/run_streamtable_span.py b/weave/legacy/weave/run_streamtable_span.py similarity index 93% rename from weave/legacy/run_streamtable_span.py rename to weave/legacy/weave/run_streamtable_span.py index ae5f9225c22..1583f6e2af4 100644 --- a/weave/legacy/run_streamtable_span.py +++ b/weave/legacy/weave/run_streamtable_span.py @@ -1,10 +1,10 @@ import typing from typing import Iterable -from weave.legacy import stream_data_interfaces -from weave.legacy.eager import WeaveIter -from weave.legacy import artifact_wandb, uris -from weave.legacy.run import Run +from weave.legacy.weave import stream_data_interfaces +from weave.legacy.weave.eager import WeaveIter +from weave.legacy.weave import artifact_wandb, uris +from weave.legacy.weave.run import Run class RunStreamTableSpan: diff --git a/weave/legacy/runfiles_wandb.py b/weave/legacy/weave/runfiles_wandb.py similarity index 97% rename from weave/legacy/runfiles_wandb.py rename to weave/legacy/weave/runfiles_wandb.py index 9353652927a..201baaed024 100644 --- a/weave/legacy/runfiles_wandb.py +++ b/weave/legacy/weave/runfiles_wandb.py @@ -6,10 +6,10 @@ import shutil import typing -from weave.legacy import environment as weave_env -from weave.legacy import filesystem -from weave.legacy import weave_types as types -from weave.legacy import ( +from weave.legacy.weave import environment as weave_env +from weave.legacy.weave import filesystem +from weave.legacy.weave import weave_types as types +from weave.legacy.weave import ( artifact_fs, errors, file_util, diff --git a/weave/legacy/runs.py b/weave/legacy/weave/runs.py similarity index 92% rename from weave/legacy/runs.py rename to weave/legacy/weave/runs.py index bb27e87b374..f20967a4d8e 100644 --- a/weave/legacy/runs.py +++ b/weave/legacy/weave/runs.py @@ -1,7 +1,7 @@ import dataclasses import typing -from weave.legacy import weave_types as types +from weave.legacy.weave import weave_types as types @dataclasses.dataclass diff --git a/weave/legacy/serialize.py b/weave/legacy/weave/serialize.py similarity index 97% rename from weave/legacy/serialize.py rename to weave/legacy/weave/serialize.py index ee83f9047cb..f39954a1375 100644 --- a/weave/legacy/serialize.py +++ b/weave/legacy/weave/serialize.py @@ -7,9 +7,9 @@ import random import typing -from weave import weave_internal -from weave.legacy import weave_types as types -from weave.legacy import storage, errors, graph, memo, value_or_error +from weave.legacy.weave import weave_internal +from weave.legacy.weave import weave_types as types +from weave.legacy.weave import storage, errors, graph, memo, value_or_error NodeOrOp = typing.Union[graph.Node, graph.Op] diff --git a/weave/legacy/server.py b/weave/legacy/weave/server.py similarity index 97% rename from weave/legacy/server.py rename to weave/legacy/weave/server.py index 51f5f6bd6e4..90c2092b1ae 100644 --- a/weave/legacy/server.py +++ b/weave/legacy/weave/server.py @@ -12,7 +12,7 @@ import requests from werkzeug.serving import make_server -from weave.legacy import ( +from weave.legacy.weave import ( cache, context, engine_trace, @@ -27,8 +27,8 @@ storage, weave_types, ) -from weave.legacy.language_features.tagging import tag_store -from weave.legacy.language_features.tagging.tag_store import isolated_tagging_context +from weave.legacy.weave.language_features.tagging import tag_store +from weave.legacy.weave.language_features.tagging.tag_store import isolated_tagging_context # A function to monkeypatch the request post method # def patch_request_post(): diff --git a/weave/legacy/server_error_handling.py b/weave/legacy/weave/server_error_handling.py similarity index 98% rename from weave/legacy/server_error_handling.py rename to weave/legacy/weave/server_error_handling.py index f7c262a24c0..1e3b10ba96f 100644 --- a/weave/legacy/server_error_handling.py +++ b/weave/legacy/weave/server_error_handling.py @@ -26,9 +26,9 @@ from werkzeug import Response from werkzeug import exceptions as werkzeug_exceptions from werkzeug import http as werkzeug_http -from weave.legacy import errors +from weave.legacy.weave import errors -from weave.legacy import util +from weave.legacy.weave import util class WeaveInternalHttpException(werkzeug_exceptions.HTTPException): diff --git a/weave/legacy/server_interface.py b/weave/legacy/weave/server_interface.py similarity index 100% rename from weave/legacy/server_interface.py rename to weave/legacy/weave/server_interface.py diff --git a/weave/legacy/show.py b/weave/legacy/weave/show.py similarity index 95% rename from weave/legacy/show.py rename to weave/legacy/weave/show.py index 204e84d5b7f..5e59d05272e 100644 --- a/weave/legacy/show.py +++ b/weave/legacy/weave/show.py @@ -5,10 +5,10 @@ from IPython.display import IFrame, display -from weave.legacy import storage, artifact_fs, context, errors, graph, ops, node_ref, panel, ref_base -from weave.legacy import util +from weave.legacy.weave import storage, artifact_fs, context, errors, graph, ops, node_ref, panel, ref_base +from weave.legacy.weave import util from . import usage_analytics -from weave.legacy import weave_types as types +from weave.legacy.weave import weave_types as types from . import weavejs_fixes @@ -21,7 +21,7 @@ def make_varname_for_type(t: types.Type): # type: ignore def make_container( obj: typing.Union[panel.Panel, graph.Node], name: str ) -> panel.Panel: - from weave.legacy.panels import Group + from weave.legacy.weave.panels import Group if isinstance(obj, graph.Node): return Group( diff --git a/weave/legacy/signal_handlers.py b/weave/legacy/weave/signal_handlers.py similarity index 99% rename from weave/legacy/signal_handlers.py rename to weave/legacy/weave/signal_handlers.py index d9c1e89e683..be47f10103a 100644 --- a/weave/legacy/signal_handlers.py +++ b/weave/legacy/weave/signal_handlers.py @@ -10,7 +10,7 @@ import typing from types import FrameType -from weave.legacy import environment +from weave.legacy.weave import environment def dump_folder() -> pathlib.Path: diff --git a/weave/legacy/stitch.py b/weave/legacy/weave/stitch.py similarity index 98% rename from weave/legacy/stitch.py rename to weave/legacy/weave/stitch.py index 9ba0145040b..69aba75e31f 100644 --- a/weave/legacy/stitch.py +++ b/weave/legacy/weave/stitch.py @@ -22,11 +22,11 @@ import dataclasses import typing -from weave.legacy import graph, op_def, registry_mem -from weave.legacy.language_features.tagging import opdef_util +from weave.legacy.weave import graph, op_def, registry_mem +from weave.legacy.weave.language_features.tagging import opdef_util -from weave.legacy import errors -from weave.legacy import weave_types as types +from weave.legacy.weave import errors +from weave.legacy.weave import weave_types as types from . import _dict_utils diff --git a/weave/legacy/storage.py b/weave/legacy/weave/storage.py similarity index 97% rename from weave/legacy/storage.py rename to weave/legacy/weave/storage.py index e13f102bb1b..b3eef9a5220 100644 --- a/weave/legacy/storage.py +++ b/weave/legacy/weave/storage.py @@ -7,8 +7,8 @@ import re import typing -from weave.client_context import weave_client as weave_client_context -from weave.legacy import ( +from weave.trace.client_context import weave_client as weave_client_context +from weave.legacy.weave import ( artifact_base, artifact_fs, artifact_local, @@ -22,12 +22,12 @@ timestamp, ) -from weave.legacy import weave_types as types +from weave.legacy.weave import weave_types as types Ref = ref_base.Ref if typing.TYPE_CHECKING: - from weave.legacy.wandb_interface.wandb_lite_run import InMemoryLazyLiteRun + from weave.legacy.weave.wandb_interface.wandb_lite_run import InMemoryLazyLiteRun def split_path_dotfile(path, dotfile_name): # type: ignore @@ -63,7 +63,7 @@ def _get_weave_type_with_refs(obj: typing.Any): # type: ignore def _ensure_object_components_are_published( # type: ignore obj: typing.Any, wb_type: types.Type, artifact: artifact_wandb.WandbArtifact ): - from weave.legacy.mappers_publisher import map_to_python_remote + from weave.legacy.weave.mappers_publisher import map_to_python_remote mapper = map_to_python_remote(wb_type, artifact) return mapper.apply(obj) @@ -451,7 +451,7 @@ def to_json_with_refs( # type: ignore """ # This is newer than to_python, save and publish above, and doesn't use the "mapper" # pattern, which is overkill. Much better to just write a simple function like this. - from weave.legacy import op_def + from weave.legacy.weave import op_def if wb_type is None: wb_type = types.TypeRegistry.type_of(obj) @@ -508,7 +508,7 @@ def convert_timestamps_to_epoch_ms(obj: typing.Any) -> typing.Any: def to_weavejs(obj, artifact: typing.Optional[artifact_base.Artifact] = None): # type: ignore - from weave.legacy.arrow import list_ as arrow_list + from weave.legacy.weave.arrow import list_ as arrow_list obj = box.unbox(obj) if isinstance(obj, (str, int, float, bool, type(None))): diff --git a/weave/legacy/stream_data_interfaces.py b/weave/legacy/weave/stream_data_interfaces.py similarity index 100% rename from weave/legacy/stream_data_interfaces.py rename to weave/legacy/weave/stream_data_interfaces.py diff --git a/weave/legacy/timestamp.py b/weave/legacy/weave/timestamp.py similarity index 100% rename from weave/legacy/timestamp.py rename to weave/legacy/weave/timestamp.py diff --git a/weave/legacy/trace_legacy.py b/weave/legacy/weave/trace_legacy.py similarity index 96% rename from weave/legacy/trace_legacy.py rename to weave/legacy/weave/trace_legacy.py index aefd89dc605..e4f0a008198 100644 --- a/weave/legacy/trace_legacy.py +++ b/weave/legacy/weave/trace_legacy.py @@ -1,11 +1,11 @@ import os import typing -from weave.legacy import artifact_local, graph, runs +from weave.legacy.weave import artifact_local, graph, runs from . import ref_base from . import errors -from weave.legacy import weave_types as types +from weave.legacy.weave import weave_types as types def get_obj_creator(ref: ref_base.Ref) -> typing.Optional[runs.Run]: diff --git a/weave/legacy/trace_local.py b/weave/legacy/weave/trace_local.py similarity index 95% rename from weave/legacy/trace_local.py rename to weave/legacy/weave/trace_local.py index 05c6075b003..3b969dc5d89 100644 --- a/weave/legacy/trace_local.py +++ b/weave/legacy/weave/trace_local.py @@ -5,14 +5,14 @@ import typing from typing import Mapping -from weave.legacy import artifact_local, graph, op_def, op_policy, runs +from weave.legacy.weave import artifact_local, graph, op_def, op_policy, runs from . import ref_base -from .. import ( +from weave.legacy.weave import ( storage, weave_internal, ) -from weave.legacy import weave_types as types +from weave.legacy.weave import weave_types as types @dataclasses.dataclass @@ -118,13 +118,13 @@ def get_run(self, run_key: RunKey) -> graph.Node[runs.Run]: return self._single_run(run_key) def get_run_val(self, run_key: RunKey) -> typing.Optional[runs.Run]: - from weave.legacy import execute_fast + from weave.legacy.weave import execute_fast res = execute_fast._execute_fn_no_engine(None, None, self.get_run(run_key)) # type: ignore[no-untyped-call] return res def save_run(self, run: runs.Run): # type: ignore - from weave.legacy.ops_primitives import weave_api + from weave.legacy.weave.ops_primitives import weave_api run_key = RunKey(run.op_name, run.id) if self._should_save_to_table(run_key): diff --git a/weave/legacy/types_numpy.py b/weave/legacy/weave/types_numpy.py similarity index 96% rename from weave/legacy/types_numpy.py rename to weave/legacy/weave/types_numpy.py index c7d661e8780..8d72409f7a3 100644 --- a/weave/legacy/types_numpy.py +++ b/weave/legacy/weave/types_numpy.py @@ -1,6 +1,6 @@ import numpy as np -from weave.legacy import weave_types as types +from weave.legacy.weave import weave_types as types # TODO: this doesn't match how extra works for list types... diff --git a/weave/legacy/uris.py b/weave/legacy/weave/uris.py similarity index 98% rename from weave/legacy/uris.py rename to weave/legacy/weave/uris.py index 465dac98b9f..f207ff32cb7 100644 --- a/weave/legacy/uris.py +++ b/weave/legacy/weave/uris.py @@ -2,7 +2,7 @@ import typing from urllib import parse -from weave.legacy import errors +from weave.legacy.weave import errors if typing.TYPE_CHECKING: from . import ref_base diff --git a/weave/legacy/urls.py b/weave/legacy/weave/urls.py similarity index 96% rename from weave/legacy/urls.py rename to weave/legacy/weave/urls.py index cf7d53cbf38..a74d7e3721c 100644 --- a/weave/legacy/urls.py +++ b/weave/legacy/weave/urls.py @@ -2,7 +2,7 @@ from wandb import util as wb_util -from weave.legacy import context_state, environment +from weave.legacy.weave import context_state, environment BROWSE3_PATH = "browse3" WEAVE_SLUG = "weave" diff --git a/weave/legacy/usage_analytics.py b/weave/legacy/weave/usage_analytics.py similarity index 95% rename from weave/legacy/usage_analytics.py rename to weave/legacy/weave/usage_analytics.py index 5fd8bddc571..f2acef97b0f 100644 --- a/weave/legacy/usage_analytics.py +++ b/weave/legacy/weave/usage_analytics.py @@ -3,7 +3,7 @@ import analytics -from weave.legacy import context_state +from weave.legacy.weave import context_state from . import environment diff --git a/weave/legacy/util.py b/weave/legacy/weave/util.py similarity index 98% rename from weave/legacy/util.py rename to weave/legacy/weave/util.py index 8027cf35a9d..f057ac40073 100644 --- a/weave/legacy/util.py +++ b/weave/legacy/weave/util.py @@ -8,7 +8,7 @@ # import ipynbname import typing -from weave.legacy.errors import WeaveFingerprintErrorMixin +from weave.legacy.weave.errors import WeaveFingerprintErrorMixin sentry_inited = False diff --git a/weave/legacy/val_const.py b/weave/legacy/weave/val_const.py similarity index 90% rename from weave/legacy/val_const.py rename to weave/legacy/weave/val_const.py index c48e34e50cd..458034826e9 100644 --- a/weave/legacy/val_const.py +++ b/weave/legacy/weave/val_const.py @@ -7,7 +7,7 @@ import typing -from weave.legacy import weave_types as types +from weave.legacy.weave import weave_types as types class Const: diff --git a/weave/legacy/value_or_error.py b/weave/legacy/weave/value_or_error.py similarity index 100% rename from weave/legacy/value_or_error.py rename to weave/legacy/weave/value_or_error.py diff --git a/weave/legacy/wandb_api.py b/weave/legacy/weave/wandb_api.py similarity index 98% rename from weave/legacy/wandb_api.py rename to weave/legacy/weave/wandb_api.py index d93a11da936..d0edbeaa8be 100644 --- a/weave/legacy/wandb_api.py +++ b/weave/legacy/weave/wandb_api.py @@ -14,12 +14,12 @@ from gql.transport.requests import RequestsHTTPTransport from requests.auth import HTTPBasicAuth -from weave.legacy import errors -from weave.legacy import environment as weave_env -from weave.legacy import wandb_client_api, engine_trace +from weave.legacy.weave import errors +from weave.legacy.weave import environment as weave_env +from weave.legacy.weave import wandb_client_api, engine_trace # Importing at the top-level namespace so other files can import from here. -from weave.legacy.context_state import WandbApiContext, _wandb_api_context +from weave.legacy.weave.context_state import WandbApiContext, _wandb_api_context tracer = engine_trace.tracer() # type: ignore diff --git a/weave/legacy/wandb_client_api.py b/weave/legacy/weave/wandb_client_api.py similarity index 98% rename from weave/legacy/wandb_client_api.py rename to weave/legacy/weave/wandb_client_api.py index 11bbff4e333..350dd9b8eba 100644 --- a/weave/legacy/wandb_client_api.py +++ b/weave/legacy/weave/wandb_client_api.py @@ -12,7 +12,7 @@ from wandb.errors import CommError as WandbCommError from wandb.sdk.internal.internal_api import _thread_local_api_settings -from weave.legacy import errors +from weave.legacy.weave import errors def wandb_public_api() -> public.Api: diff --git a/weave/legacy/wandb_file_manager.py b/weave/legacy/weave/wandb_file_manager.py similarity index 99% rename from weave/legacy/wandb_file_manager.py rename to weave/legacy/weave/wandb_file_manager.py index ecce61b90d0..af631151048 100644 --- a/weave/legacy/wandb_file_manager.py +++ b/weave/legacy/weave/wandb_file_manager.py @@ -12,8 +12,8 @@ from requests.auth import HTTPBasicAuth from wandb.sdk.lib import hashutil -from weave.legacy import environment as weave_env -from weave.legacy import filesystem, artifact_wandb, cache, errors, wandb_api, engine_trace, weave_http +from weave.legacy.weave import environment as weave_env +from weave.legacy.weave import filesystem, artifact_wandb, cache, errors, wandb_api, engine_trace, weave_http tracer = engine_trace.tracer() # type: ignore diff --git a/weave/legacy/wandb_interface/project_creator.py b/weave/legacy/weave/wandb_interface/project_creator.py similarity index 100% rename from weave/legacy/wandb_interface/project_creator.py rename to weave/legacy/weave/wandb_interface/project_creator.py diff --git a/weave/legacy/wandb_interface/wandb_artifact_pusher.py b/weave/legacy/weave/wandb_interface/wandb_artifact_pusher.py similarity index 95% rename from weave/legacy/wandb_interface/wandb_artifact_pusher.py rename to weave/legacy/weave/wandb_interface/wandb_artifact_pusher.py index fa80c077c16..0bbda99b705 100644 --- a/weave/legacy/wandb_interface/wandb_artifact_pusher.py +++ b/weave/legacy/weave/wandb_interface/wandb_artifact_pusher.py @@ -5,9 +5,9 @@ from wandb import Artifact from wandb.apis.public import api as wb_public -from weave.legacy import engine_trace -from weave.legacy import wandb_client_api -from weave.legacy.wandb_interface.wandb_lite_run import InMemoryLazyLiteRun +from weave.legacy.weave import engine_trace +from weave.legacy.weave import wandb_client_api +from weave.legacy.weave.wandb_interface.wandb_lite_run import InMemoryLazyLiteRun def artifact_commithash_by_digest( diff --git a/weave/legacy/wandb_interface/wandb_lite_run.py b/weave/legacy/weave/wandb_interface/wandb_lite_run.py similarity index 98% rename from weave/legacy/wandb_interface/wandb_lite_run.py rename to weave/legacy/weave/wandb_interface/wandb_lite_run.py index 4cd60b7452b..9703a9d88e4 100644 --- a/weave/legacy/wandb_interface/wandb_lite_run.py +++ b/weave/legacy/weave/wandb_interface/wandb_lite_run.py @@ -15,8 +15,8 @@ from wandb.sdk.internal.sender import _manifest_json_from_proto from wandb.sdk.lib import runid -from weave.legacy import errors -from weave.legacy import engine_trace, wandb_client_api +from weave.legacy.weave import errors +from weave.legacy.weave import engine_trace, wandb_client_api logger = logging.getLogger(__name__) diff --git a/weave/legacy/wandb_interface/wandb_stream_table.py b/weave/legacy/weave/wandb_interface/wandb_stream_table.py similarity index 97% rename from weave/legacy/wandb_interface/wandb_stream_table.py rename to weave/legacy/weave/wandb_interface/wandb_stream_table.py index 63a05b9fb8b..2669282ee2d 100644 --- a/weave/legacy/wandb_interface/wandb_stream_table.py +++ b/weave/legacy/weave/wandb_interface/wandb_stream_table.py @@ -15,8 +15,8 @@ from wandb.sdk.lib.paths import LogicalPath from wandb.sdk.lib.printer import get_printer -from weave.legacy import weave_types -from weave.legacy import ( +from weave.legacy.weave import weave_types +from weave.legacy.weave import ( storage, errors, artifact_base, @@ -27,8 +27,8 @@ runfiles_wandb, wandb_api, ) -from weave.legacy.core_types.stream_table_type import StreamTableType -from weave.legacy.wandb_interface.wandb_lite_run import InMemoryLazyLiteRun +from weave.legacy.weave.core_types.stream_table_type import StreamTableType +from weave.legacy.weave.wandb_interface.wandb_lite_run import InMemoryLazyLiteRun if typing.TYPE_CHECKING: from wandb.sdk.internal.file_pusher import FilePusher @@ -201,8 +201,8 @@ def log(self, row_or_rows: ROW_TYPE) -> None: self._log_row(row) def rows(self) -> graph.Node: - from weave.legacy.ops_domain import stream_table_ops - from weave.legacy.ops_primitives import weave_api + from weave.legacy.weave.ops_domain import stream_table_ops + from weave.legacy.weave.ops_primitives import weave_api if self._weave_stream_table_ref is None: raise errors.WeaveInternalError("ref is None after ensure") @@ -211,7 +211,7 @@ def rows(self) -> graph.Node: ) def _ipython_display_(self) -> graph.Node: - from weave.legacy import show + from weave.legacy.weave import show return show.show(self.rows()) diff --git a/weave/legacy/wandb_util.py b/weave/legacy/weave/wandb_util.py similarity index 98% rename from weave/legacy/wandb_util.py rename to weave/legacy/weave/wandb_util.py index be4161ff038..11c6c717eb9 100644 --- a/weave/legacy/wandb_util.py +++ b/weave/legacy/weave/wandb_util.py @@ -1,8 +1,8 @@ import typing -from weave.legacy import errors -from weave.legacy import weave_types as types -from weave.legacy import ops, ops_domain +from weave.legacy.weave import errors +from weave.legacy.weave import weave_types as types +from weave.legacy.weave import ops, ops_domain class Weave0TypeJson(typing.TypedDict): diff --git a/weave/legacy/weave_http.py b/weave/legacy/weave/weave_http.py similarity index 98% rename from weave/legacy/weave_http.py rename to weave/legacy/weave/weave_http.py index 773625fe9ec..f425dd2567b 100644 --- a/weave/legacy/weave_http.py +++ b/weave/legacy/weave/weave_http.py @@ -13,7 +13,7 @@ import requests.auth import yarl -from weave.legacy import engine_trace, filesystem, server_error_handling +from weave.legacy.weave import engine_trace, filesystem, server_error_handling logging.getLogger("aiohttp.access").setLevel(logging.WARNING) logging.getLogger("aiohttp.client").setLevel(logging.WARNING) diff --git a/weave/legacy/weave_inspector.py b/weave/legacy/weave/weave_inspector.py similarity index 99% rename from weave/legacy/weave_inspector.py rename to weave/legacy/weave/weave_inspector.py index d878e4f8d4d..fdcde5393c6 100644 --- a/weave/legacy/weave_inspector.py +++ b/weave/legacy/weave/weave_inspector.py @@ -105,9 +105,9 @@ import tabulate -from weave.legacy import graph +from weave.legacy.weave import graph -from weave.legacy import weave_types as types +from weave.legacy.weave import weave_types as types from .partial_object import PartialObjectType @@ -137,7 +137,7 @@ def _node_type_name(node: graph.Node) -> str: def _type_name(node_type: types.Type) -> str: - from weave.legacy.language_features.tagging import tagged_value_type + from weave.legacy.weave.language_features.tagging import tagged_value_type if isinstance(node_type, tagged_value_type.TaggedValueType): return f"TV({_type_name(node_type.value)})" diff --git a/weave/weave_internal.py b/weave/legacy/weave/weave_internal.py similarity index 93% rename from weave/weave_internal.py rename to weave/legacy/weave/weave_internal.py index 014ab621db6..4d485658814 100644 --- a/weave/weave_internal.py +++ b/weave/legacy/weave/weave_internal.py @@ -1,7 +1,7 @@ import typing -from weave.legacy import client_interface, context_state, errors, graph -from weave.legacy import weave_types as types +from weave.legacy.weave import client_interface, context_state, errors, graph +from weave.legacy.weave import weave_types as types def dereference_variables( @@ -83,14 +83,14 @@ def use( def make_var_node(type_: types.Type, name: str) -> graph.VarNode: # Circular import. TODO: fix - from weave.legacy import dispatch + from weave.legacy.weave import dispatch return dispatch.RuntimeVarNode(type_, name) def make_const_node(type_: types.Type, val: typing.Any) -> graph.ConstNode: # Circular import. TODO: fix - from weave.legacy import dispatch + from weave.legacy.weave import dispatch return dispatch.RuntimeConstNode(type_, val) @@ -114,7 +114,7 @@ def make_output_node( type_: types.Type, op_name: str, op_params: dict[str, graph.Node] ) -> graph.OutputNode: # Circular import. TODO: fix - from weave.legacy import dispatch + from weave.legacy.weave import dispatch return dispatch.RuntimeOutputNode(type_, op_name, op_params) @@ -125,7 +125,7 @@ def define_fn( ) -> graph.ConstNode: var_nodes = [make_var_node(t, k) for k, t in parameters.items()] try: - from weave.legacy import op_def + from weave.legacy.weave import op_def with op_def.no_refine(): fnNode = body(*var_nodes) @@ -148,7 +148,7 @@ def define_fn( # and the function doesn't explicitly operate on tagged values. this ensures that the input tags # are propagated appropriately to the output type of the function. def refine_graph(node: graph.Node) -> graph.Node: - from .legacy.registry_mem import memory_registry + from weave.legacy.weave.registry_mem import memory_registry if isinstance(node, (graph.ConstNode, graph.VoidNode, graph.VarNode)): return node @@ -176,6 +176,6 @@ def manual_call( You can produce incorrect nodes this way. Use with caution. """ - from weave.legacy import dispatch + from weave.legacy.weave import dispatch return dispatch.RuntimeOutputNode(output_type, op_name, inputs) diff --git a/weave/legacy/weave_pydantic.py b/weave/legacy/weave/weave_pydantic.py similarity index 95% rename from weave/legacy/weave_pydantic.py rename to weave/legacy/weave/weave_pydantic.py index 870b2a83d61..0c9b0f7c7ad 100644 --- a/weave/legacy/weave_pydantic.py +++ b/weave/legacy/weave/weave_pydantic.py @@ -1,6 +1,6 @@ from pydantic import BaseModel, create_model -from weave.legacy import weave_types as types +from weave.legacy.weave import weave_types as types from . import infer_types diff --git a/weave/legacy/weave_types.py b/weave/legacy/weave/weave_types.py similarity index 99% rename from weave/legacy/weave_types.py rename to weave/legacy/weave/weave_types.py index 2b1c45c3891..ad46a314df6 100644 --- a/weave/legacy/weave_types.py +++ b/weave/legacy/weave/weave_types.py @@ -11,20 +11,20 @@ import pydantic from dateutil.parser import isoparse -from weave.legacy import ( +from weave.legacy.weave import ( box, context_state, errors, mappers_python, object_type_ref_util, ) -from weave.legacy import timestamp as weave_timestamp +from weave.legacy.weave import timestamp as weave_timestamp if typing.TYPE_CHECKING: - from weave.legacy import artifact_base - from weave.legacy.artifact_fs import FilesystemArtifact + from weave.legacy.weave import artifact_base + from weave.legacy.weave.artifact_fs import FilesystemArtifact - from weave.legacy import weave_inspector + from weave.legacy.weave import weave_inspector def to_weavejs_typekey(k: str) -> str: @@ -126,7 +126,7 @@ def type_class_of(obj): # type: ignore @staticmethod def type_of(obj: typing.Any) -> "Type": # type: ignore - from weave.legacy import ref_base + from weave.legacy.weave import ref_base if ( context_state.ref_tracking_enabled() @@ -460,7 +460,7 @@ def _make(cls, kwargs={}): # type: ignore def _inspect(self) -> "weave_inspector.TypeInspector": # type: ignore """Only intended to be used by developers to help debug the graph.""" # Circular import, so we do it here. - from weave.legacy import weave_inspector + from weave.legacy.weave import weave_inspector return weave_inspector.TypeInspector(self) @@ -1061,7 +1061,7 @@ def property_types(self) -> dict[str, Type]: # type: ignore @classmethod def typeclass_of_class(cls, check_class): # type: ignore - from weave.legacy import weave_pydantic + from weave.legacy.weave import weave_pydantic if not issubclass(check_class, pydantic.BaseModel): return cls @@ -1278,7 +1278,7 @@ def _hashable(self): # type: ignore @classmethod def type_of_instance(cls, obj): # type: ignore - from weave.legacy import infer_types + from weave.legacy.weave import infer_types attr_types = {} for field in dataclasses.fields(obj): @@ -1361,7 +1361,7 @@ def save_instance(self, obj, artifact, name): # type: ignore def load_instance(self, artifact, name, extra=None): # type: ignore with artifact.open(f"{name}.object.json") as f: # TODO: no circular imports! - from weave.legacy import graph + from weave.legacy.weave import graph return graph.Node.node_from_json(json.load(f)) @@ -1385,7 +1385,7 @@ def _is_assignable_to(self, other_type) -> typing.Optional[bool]: # type: ignor return None def save_instance(self, obj, artifact, name): # type: ignore - from weave.legacy import ref_base + from weave.legacy.weave import ref_base obj_ref = ref_base.get_ref(obj) if obj_ref is None: @@ -1419,7 +1419,7 @@ class LocalArtifactRefType(FilesystemArtifactRefType): @dataclasses.dataclass(frozen=True) class WandbArtifactRefType(FilesystemArtifactRefType): def load_instance(self, artifact, name, extra=None): # type: ignore - from weave.legacy import artifact_wandb + from weave.legacy.weave import artifact_wandb return artifact_wandb.WandbArtifactRef(artifact, name) @@ -1571,7 +1571,7 @@ def merge_types(a: Type, b: Type) -> Type: This implementation must match list.concat implementations (which is the only way to extend a list in Weave). Ie list.concat(list[a], [b]) -> list[merge_types(a, b)] """ - from weave.legacy.language_features.tagging import tagged_value_type + from weave.legacy.weave.language_features.tagging import tagged_value_type if a == b: return a diff --git a/weave/legacy/weavejs_fixes.py b/weave/legacy/weave/weavejs_fixes.py similarity index 99% rename from weave/legacy/weavejs_fixes.py rename to weave/legacy/weave/weavejs_fixes.py index 23c97ee64c7..6957e839a8d 100644 --- a/weave/legacy/weavejs_fixes.py +++ b/weave/legacy/weave/weavejs_fixes.py @@ -8,9 +8,9 @@ import math import typing -from weave.legacy import graph +from weave.legacy.weave import graph -from weave.legacy import weave_types +from weave.legacy.weave import weave_types def _convert_specific_opname_to_generic_opname( diff --git a/weave/legacy/weavify.py b/weave/legacy/weave/weavify.py similarity index 93% rename from weave/legacy/weavify.py rename to weave/legacy/weave/weavify.py index 6714d6676f8..af81e62d492 100644 --- a/weave/legacy/weavify.py +++ b/weave/legacy/weave/weavify.py @@ -1,12 +1,12 @@ import typing -from weave.legacy import errors, graph, op_args, val_const +from weave.legacy.weave import errors, graph, op_args, val_const -from .. import weave_internal -from weave.legacy import weave_types as types +from weave.legacy.weave import weave_internal +from weave.legacy.weave import weave_types as types if typing.TYPE_CHECKING: - from weave.legacy import op_def + from weave.legacy.weave import op_def def verify_weave_fn_is_valid(op: "op_def.OpDef", weavified: graph.Node) -> None: @@ -79,7 +79,7 @@ def weave_fn_body(*args: graph.VarNode) -> graph.Node: def weavify_object(obj: typing.Any) -> graph.Node: - from weave.legacy.ops_primitives import dict_, make_list + from weave.legacy.weave.ops_primitives import dict_, make_list if isinstance(obj, graph.Node): return obj diff --git a/weave/test_scripts/wandb_artifact_perf.py b/weave/test_scripts/wandb_artifact_perf.py index bc38aeb1d64..af3f3e0aa7d 100644 --- a/weave/test_scripts/wandb_artifact_perf.py +++ b/weave/test_scripts/wandb_artifact_perf.py @@ -13,7 +13,7 @@ import cProfile import time -from weave.legacy import ( +from weave.legacy.weave import ( artifact_wandb, async_map, engine_trace, diff --git a/weave/tests/fixture_fakewandb.py b/weave/tests/fixture_fakewandb.py index bbce4d66b91..4406f36613a 100644 --- a/weave/tests/fixture_fakewandb.py +++ b/weave/tests/fixture_fakewandb.py @@ -11,20 +11,19 @@ import wandb import weave +from weave.legacy.tests.util.tag_test_util import op_add_tag # Note: We're mocking out the whole io_service right now. This is too # high level and doesn't test the actual io implementation. We should # mock wandb_api instead probably. -from weave.legacy import io_service, util, wandb_api, wandb_client_api -from weave.legacy.artifact_wandb import ( +from weave.legacy.weave import io_service, util, wandb_api, wandb_client_api +from weave.legacy.weave.artifact_wandb import ( WandbArtifact, WandbArtifactManifest, WeaveWBArtifactByIDURI, WeaveWBArtifactURI, ) -from .tag_test_util import op_add_tag - TEST_TABLE_ARTIFACT_PATH = "testdata/wb_artifacits/test_res_1fwmcd3q:v0" ABS_TEST_TABLE_ARTIFACT_PATH = os.path.abspath(TEST_TABLE_ARTIFACT_PATH) diff --git a/weave/tests/legacy/test_weave_api.py b/weave/tests/legacy/test_weave_api.py deleted file mode 100644 index 8f389d26a2f..00000000000 --- a/weave/tests/legacy/test_weave_api.py +++ /dev/null @@ -1,54 +0,0 @@ -import os - -import pytest - -import weave -import weave.legacy.context_state -import weave.legacy.wandb_api -import weave.weave_init - - -def test_create_list_rename_delete(): - os.environ["WEAVE_CACHE_MODE"] = "minimal" - - # create - art_node = weave.legacy.ops.get("local-artifact:///my-data:latest/obj") - art_node.set("foo") - assert weave.use(art_node) == "foo" - - # list - arts = weave.use(weave.legacy.ops.local_artifacts()) - assert len(arts) == 1 - assert arts[0].name == "my-data" - - # rename - weave.legacy.ops.rename_artifact(art_node, "my-data2") - arts = weave.use(weave.legacy.ops.local_artifacts()) - assert len(arts) == 1 - assert arts[0].name == "my-data2" - - # delete - art_node = weave.legacy.ops.get("local-artifact:///my-data2:latest/obj") - weave.legacy.ops.delete_artifact(art_node) - arts = weave.use(weave.legacy.ops.local_artifacts()) - assert len(arts) == 0 - - -def test_weave_finish_unsets_client(client): - @weave.op - def foo(): - return 1 - - weave.client_context.weave_client.set_weave_client_global(None) - weave.weave_init._current_inited_client = weave.weave_init.InitializedClient(client) - weave_client = weave.weave_init._current_inited_client.client - assert weave.weave_init._current_inited_client is not None - - foo() - assert len(list(weave_client.calls())) == 1 - - weave.finish() - - foo() - assert len(list(weave_client.calls())) == 1 - assert weave.weave_init._current_inited_client is None diff --git a/weave/tests/trace/op_versioning_solo.py b/weave/tests/trace/op_versioning_solo.py index b2ceec4b2d2..bacc0e27155 100644 --- a/weave/tests/trace/op_versioning_solo.py +++ b/weave/tests/trace/op_versioning_solo.py @@ -1,7 +1,7 @@ import numpy as np import weave -from weave.legacy import artifact_fs +from weave.legacy.weave import artifact_fs @weave.op() diff --git a/weave/tests/legacy/test_cli.py b/weave/tests/trace/test_cli.py similarity index 88% rename from weave/tests/legacy/test_cli.py rename to weave/tests/trace/test_cli.py index 71e0391632c..9710611e85e 100644 --- a/weave/tests/legacy/test_cli.py +++ b/weave/tests/trace/test_cli.py @@ -1,6 +1,6 @@ from click.testing import CliRunner -from weave.cli import cli +from weave.trace.cli import cli from weave.version import VERSION diff --git a/weave/tests/trace/test_client_trace.py b/weave/tests/trace/test_client_trace.py index d20ad76a8a9..ef6d551fbad 100644 --- a/weave/tests/trace/test_client_trace.py +++ b/weave/tests/trace/test_client_trace.py @@ -1558,17 +1558,17 @@ def test_ref_get_no_client(trace_init_client): @contextmanager def _no_graph_client(): - client = weave.client_context.weave_client.get_weave_client() - weave.client_context.weave_client.set_weave_client_global(None) + client = weave.trace.client_context.weave_client.get_weave_client() + weave.trace.client_context.weave_client.set_weave_client_global(None) try: yield finally: - weave.client_context.weave_client.set_weave_client_global(client) + weave.trace.client_context.weave_client.set_weave_client_global(client) @contextmanager def _patched_default_initializer(trace_client: weave_client.WeaveClient): - from weave import weave_init + from weave.trace import weave_init def init_weave_get_server_patched(api_key): return trace_client.server diff --git a/weave/tests/trace/test_op_versioning.py b/weave/tests/trace/test_op_versioning.py index f1f2203a323..3fc023e235c 100644 --- a/weave/tests/trace/test_op_versioning.py +++ b/weave/tests/trace/test_op_versioning.py @@ -5,7 +5,7 @@ import pytest import weave -from weave.legacy import artifact_fs, derive_op, op_def +from weave.legacy.weave import artifact_fs, derive_op, op_def from weave.trace_server.trace_server_interface import FileContentReadReq, ObjReadReq diff --git a/weave/tests/trace/test_server.py b/weave/tests/trace/test_server.py index 1be4acbb3cc..c5aa37a3c74 100644 --- a/weave/tests/trace/test_server.py +++ b/weave/tests/trace/test_server.py @@ -6,13 +6,13 @@ import pytest import requests -from weave import api as weave -from weave.legacy import client as _client -from weave.legacy import context_state, ops -from weave.legacy import server as _server -from weave.legacy import weave_types as types -from weave.legacy.decorator_op import op -from weave.weave_internal import make_const_node +from weave.legacy.weave import api as weave +from weave.legacy.weave import client as _client +from weave.legacy.weave import context_state, ops +from weave.legacy.weave import server as _server +from weave.legacy.weave import weave_types as types +from weave.legacy.weave.decorator_op import op +from weave.legacy.weave.weave_internal import make_const_node SERVER_TYPES = ["inprocess", "subprocess", "http"] diff --git a/weave/tests/trace/test_weave_client.py b/weave/tests/trace/test_weave_client.py index b0bd9210ec5..17184676066 100644 --- a/weave/tests/trace/test_weave_client.py +++ b/weave/tests/trace/test_weave_client.py @@ -12,7 +12,7 @@ import weave import weave.trace_server.trace_server_interface as tsi from weave import Evaluation -from weave.legacy import op_def +from weave.legacy.weave import op_def from weave.trace import refs, weave_client from weave.trace.isinstance import weave_isinstance from weave.trace.op import Op diff --git a/weave/tests/trace/test_weaveflow.py b/weave/tests/trace/test_weaveflow.py index a41f4eba9d4..e35b7721551 100644 --- a/weave/tests/trace/test_weaveflow.py +++ b/weave/tests/trace/test_weaveflow.py @@ -5,7 +5,7 @@ from pydantic import Field import weave -from weave.legacy import ref_base +from weave.legacy.weave import ref_base pytestmark = pytest.mark.trace diff --git a/weave/tests/trace/trace_server_clickhouse_conftest.py b/weave/tests/trace/trace_server_clickhouse_conftest.py index 7d28d4506a9..14a6b0d9fcb 100644 --- a/weave/tests/trace/trace_server_clickhouse_conftest.py +++ b/weave/tests/trace/trace_server_clickhouse_conftest.py @@ -10,13 +10,13 @@ import requests from weave.trace import weave_client +from weave.trace.weave_init import InitializedClient from weave.trace_server import ( clickhouse_trace_server_batched, external_to_internal_trace_server_adapter, ) from weave.trace_server import environment as wf_env from weave.trace_server import trace_server_interface as tsi -from weave.weave_init import InitializedClient @pytest.fixture(scope="session") diff --git a/weave/tests/wandb_system_tests_conftest.py b/weave/tests/wandb_system_tests_conftest.py index d2c18bbf3e4..a67debe9871 100644 --- a/weave/tests/wandb_system_tests_conftest.py +++ b/weave/tests/wandb_system_tests_conftest.py @@ -15,7 +15,7 @@ import requests import wandb -from weave.legacy.wandb_api import ( +from weave.legacy.weave.wandb_api import ( WandbApiContext, from_environment, wandb_api_context, diff --git a/weave/trace/api.py b/weave/trace/api.py index e6615a7221a..1c7dbbb4bb0 100644 --- a/weave/trace/api.py +++ b/weave/trace/api.py @@ -6,17 +6,13 @@ import time from typing import Any, Callable, Iterator, Optional, Union -from weave.client_context import weave_client as weave_client_context -from weave.legacy import urls, util +from weave.legacy.weave import urls, util from weave.trace.call_context import get_current_call +from weave.trace.client_context import weave_client as weave_client_context # TODO: type_serializers is imported here to trigger registration of the image serializer. # There is probably a better place for this, but including here for now to get the fix in. -from .. import ( - type_serializers, # noqa: F401 - weave_init, -) -from . import context, weave_client +from . import context, weave_client, weave_init from .constants import TRACE_OBJECT_EMOJI from .op import Op, op from .refs import ObjectRef, parse_uri @@ -206,7 +202,7 @@ def serve( ) -> str: import uvicorn - from weave.legacy import wandb_api + from weave.legacy.weave import wandb_api from .serve_fastapi import object_method_app diff --git a/weave/trace/box.py b/weave/trace/box.py index 1c39bf5e39f..a185c50a822 100644 --- a/weave/trace/box.py +++ b/weave/trace/box.py @@ -10,7 +10,7 @@ import numpy as np -from weave.legacy.ref_base import Ref +from weave.legacy.weave.ref_base import Ref T = TypeVar("T") diff --git a/weave/cli.py b/weave/trace/cli.py similarity index 96% rename from weave/cli.py rename to weave/trace/cli.py index 8975d0da554..edccda7a382 100644 --- a/weave/cli.py +++ b/weave/trace/cli.py @@ -4,11 +4,10 @@ import click from weave import __version__ +from weave.deploy import gcp as google +from weave.trace import api from weave.trace.refs import ObjectRef, parse_uri -from . import api -from .deploy import gcp as google - # from .model_server import app # TODO: does this work? @@ -26,7 +25,7 @@ def cli() -> None: # def start_ui() -> None: # print("Starting server...") # try: -# from weave.legacy import server +# from weave.legacy.weave import server # except ModuleNotFoundError: # print("Run 'pip install weave[engine]' to use the local server.") # sys.exit(1) @@ -134,7 +133,7 @@ def gcp( ) @click.option("--dev", is_flag=True, help="Run the function locally.") def modal(model_ref: str, project: str, auth_entity: str, dev: bool = False) -> None: - from .deploy import modal as mdp + from weave.deploy import modal as mdp if dev: print(f"Developing model {model_ref}...") diff --git a/weave/client_context/weave_client.py b/weave/trace/client_context/weave_client.py similarity index 92% rename from weave/client_context/weave_client.py rename to weave/trace/client_context/weave_client.py index b80375adb56..f74759f26ac 100644 --- a/weave/client_context/weave_client.py +++ b/weave/trace/client_context/weave_client.py @@ -1,8 +1,8 @@ import threading from typing import TYPE_CHECKING, Optional -from weave.legacy import context_state -from weave.legacy.errors import WeaveInitError +from weave.legacy.weave import context_state +from weave.legacy.weave.errors import WeaveInitError if TYPE_CHECKING: from weave.trace.weave_client import WeaveClient diff --git a/weave/trace/custom_objs.py b/weave/trace/custom_objs.py index 2eb2ddef24b..77fcb417c84 100644 --- a/weave/trace/custom_objs.py +++ b/weave/trace/custom_objs.py @@ -4,9 +4,9 @@ import tempfile from typing import Any, Dict, Generator, Iterator, Mapping, Optional, Union -from weave.client_context.weave_client import require_weave_client -from weave.legacy import artifact_fs +from weave.legacy.weave import artifact_fs from weave.trace import op_type # noqa: F401, Must import this to register op save/load +from weave.trace.client_context.weave_client import require_weave_client from weave.trace.op import Op, op from weave.trace.refs import ObjectRef, parse_uri from weave.trace.serializer import get_serializer_by_id, get_serializer_for_obj @@ -136,7 +136,7 @@ def decode_custom_obj( encoded_path_contents: Mapping[str, Union[str, bytes]], load_instance_op_uri: Optional[str], ) -> Any: - from weave.legacy import artifact_fs + from weave.legacy.weave import artifact_fs load_instance_op = None if load_instance_op_uri is not None: diff --git a/weave/trace/feedback.py b/weave/trace/feedback.py index 8f2eafd378f..99893df018d 100644 --- a/weave/trace/feedback.py +++ b/weave/trace/feedback.py @@ -5,9 +5,9 @@ from rich.table import Table -from weave.client_context import weave_client as weave_client_context -from weave.legacy import util +from weave.legacy.weave import util from weave.trace import rich_pydantic_util +from weave.trace.client_context import weave_client as weave_client_context from weave.trace.refs import parse_uri from weave.trace.rich_container import AbstractRichContainer from weave.trace.rich_refs import Refs diff --git a/weave/init_message.py b/weave/trace/init_message.py similarity index 98% rename from weave/init_message.py rename to weave/trace/init_message.py index d76b8e963be..92f972e71c7 100644 --- a/weave/init_message.py +++ b/weave/trace/init_message.py @@ -1,6 +1,6 @@ import typing -from weave.legacy import urls +from weave.legacy.weave import urls if typing.TYPE_CHECKING: import packaging.version # type: ignore[import-not-found] diff --git a/weave/trace/op.py b/weave/trace/op.py index 32a068d4dd3..b32908a537f 100644 --- a/weave/trace/op.py +++ b/weave/trace/op.py @@ -17,9 +17,9 @@ runtime_checkable, ) -from weave.client_context import weave_client as weave_client_context -from weave.legacy import context_state +from weave.legacy.weave import context_state from weave.trace import box, call_context, settings +from weave.trace.client_context import weave_client as weave_client_context from weave.trace.context import call_attributes from weave.trace.errors import OpCallError from weave.trace.refs import ObjectRef @@ -323,7 +323,7 @@ async def extract(): ``` """ if context_state.get_loading_built_ins(): - from weave.legacy.decorator_op import op as legacy_op + from weave.legacy.weave.decorator_op import op as legacy_op return legacy_op(*args, **kwargs) # type: ignore diff --git a/weave/trace/op_type.py b/weave/trace/op_type.py index 90608f9c99b..fd948b8a2e0 100644 --- a/weave/trace/op_type.py +++ b/weave/trace/op_type.py @@ -13,7 +13,7 @@ from _ast import AsyncFunctionDef, ExceptHandler from typing import Any, Callable, Optional, Union, get_args, get_origin -from weave.legacy import artifact_fs, context_state, errors, storage +from weave.legacy.weave import artifact_fs, context_state, errors, storage from weave.trace.ipython import ( ClassNotFoundError, get_class_source, @@ -21,7 +21,7 @@ ) from weave.trace.refs import ObjectRef -from ..legacy import environment +from ..legacy.weave import environment from . import serializer from .op import Op diff --git a/weave/trace/ref_util.py b/weave/trace/ref_util.py index ebaec7dfe1e..47aed3131dd 100644 --- a/weave/trace/ref_util.py +++ b/weave/trace/ref_util.py @@ -2,7 +2,7 @@ import typing from urllib import parse -from weave.legacy import box +from weave.legacy.weave import box from weave.trace_server import refs_internal DICT_KEY_EDGE_NAME = refs_internal.DICT_KEY_EDGE_NAME @@ -22,7 +22,7 @@ def parse_local_ref_str(s: str) -> typing.Tuple[str, typing.Optional[list[str]]] def val_with_relative_ref( parent_object: typing.Any, child_object: typing.Any, ref_extra_parts: list[str] ) -> typing.Any: - from weave.legacy import context_state, ref_base + from weave.legacy.weave import context_state, ref_base # If we already have a ref, resolve it if isinstance(child_object, ref_base.Ref): @@ -31,7 +31,7 @@ def val_with_relative_ref( # Only do this if ref_tracking_enabled right now. I just want to # avoid introducing new behavior into W&B prod for the moment. if context_state.ref_tracking_enabled(): - from weave.legacy import storage + from weave.legacy.weave import storage child_ref = storage.get_ref(child_object) parent_ref = ref_base.get_ref(parent_object) diff --git a/weave/trace/refs.py b/weave/trace/refs.py index cfffe99436c..0634de90a80 100644 --- a/weave/trace/refs.py +++ b/weave/trace/refs.py @@ -63,8 +63,8 @@ def get(self) -> Any: # Move import here so that it only happens when the function is called. # This import is invalid in the trace server and represents a dependency # that should be removed. - from weave.client_context.weave_client import get_weave_client - from weave.weave_init import init_weave + from weave.trace.client_context.weave_client import get_weave_client + from weave.trace.weave_init import init_weave gc = get_weave_client() if gc is not None: diff --git a/weave/trace/rich_pydantic_util.py b/weave/trace/rich_pydantic_util.py index 2c5e21fa2ed..ec88c0545db 100644 --- a/weave/trace/rich_pydantic_util.py +++ b/weave/trace/rich_pydantic_util.py @@ -6,7 +6,7 @@ from rich.console import Console from rich.table import Table -from weave.legacy import util +from weave.legacy.weave import util def dict_to_table(d: dict[str, Any]) -> Table: diff --git a/weave/trace/rich_refs.py b/weave/trace/rich_refs.py index c72e07f4227..aab9f11d7f8 100644 --- a/weave/trace/rich_refs.py +++ b/weave/trace/rich_refs.py @@ -4,7 +4,7 @@ from rich.table import Table -from weave.client_context import weave_client as weave_client_context +from weave.trace.client_context import weave_client as weave_client_context from weave.trace.refs import AnyRef, CallRef, parse_uri from weave.trace.rich_container import AbstractRichContainer from weave.trace.vals import WeaveObject diff --git a/weave/trace/serve_fastapi.py b/weave/trace/serve_fastapi.py index 89ef88b1d8c..98adc7537b1 100644 --- a/weave/trace/serve_fastapi.py +++ b/weave/trace/serve_fastapi.py @@ -12,12 +12,12 @@ except ImportError: from typing_extensions import Annotated # type: ignore -from weave.legacy import cache, op_args, pyfunc_type_util -from weave.legacy.wandb_api import WandbApiAsync +from weave.legacy.weave import cache, op_args, pyfunc_type_util +from weave.legacy.weave.wandb_api import WandbApiAsync from weave.trace.op import Op from weave.trace.refs import ObjectRef -from ..legacy import errors, weave_pydantic +from ..legacy.weave import errors, weave_pydantic key_cache: cache.LruTimeWindowCache[str, typing.Optional[bool]] = ( cache.LruTimeWindowCache(datetime.timedelta(minutes=5)) diff --git a/weave/trace/tests/test_vals.py b/weave/trace/tests/test_vals.py index 37a3f9c75d4..32e1b815fed 100644 --- a/weave/trace/tests/test_vals.py +++ b/weave/trace/tests/test_vals.py @@ -31,6 +31,6 @@ def my_op(obj: MyObj) -> None: # simulate a situation where the client is closed # but a reference to a trace object still exists. - weave.client_context.weave_client.set_weave_client_global(None) + weave.trace.client_context.weave_client.set_weave_client_global(None) assert my_obj.val == 1 diff --git a/weave/trace/vals.py b/weave/trace/vals.py index c92f9a024ec..a54d01b0626 100644 --- a/weave/trace/vals.py +++ b/weave/trace/vals.py @@ -8,8 +8,8 @@ from pydantic import BaseModel from pydantic import v1 as pydantic_v1 -from weave.client_context.weave_client import get_weave_client from weave.trace import box +from weave.trace.client_context.weave_client import get_weave_client from weave.trace.errors import InternalError from weave.trace.object_record import ObjectRecord from weave.trace.op import Op, call, maybe_bind_method diff --git a/weave/trace/weave_client.py b/weave/trace/weave_client.py index b856c059df4..a16884b955d 100644 --- a/weave/trace/weave_client.py +++ b/weave/trace/weave_client.py @@ -10,9 +10,9 @@ from requests import HTTPError from weave import version -from weave.client_context import weave_client as weave_client_context -from weave.legacy import urls +from weave.legacy.weave import urls from weave.trace import call_context, trace_sentry +from weave.trace.client_context import weave_client as weave_client_context from weave.trace.exception import exception_to_json_str from weave.trace.feedback import FeedbackQuery, RefFeedbackQuery from weave.trace.object_record import ( @@ -52,7 +52,7 @@ ) if typing.TYPE_CHECKING: - from ..legacy import ref_base + from ..legacy.weave import ref_base # Controls if objects can have refs to projects not the WeaveClient project. diff --git a/weave/weave_init.py b/weave/trace/weave_init.py similarity index 93% rename from weave/weave_init.py rename to weave/trace/weave_init.py index 3d0a0233aa5..55188cad443 100644 --- a/weave/weave_init.py +++ b/weave/trace/weave_init.py @@ -1,11 +1,9 @@ import typing -from weave.client_context import weave_client as weave_client_context -from weave.legacy import errors - -from . import init_message -from .trace import autopatch, trace_sentry, weave_client -from .trace_server import remote_http_trace_server, sqlite_trace_server +from weave.legacy.weave import errors +from weave.trace import autopatch, init_message, trace_sentry, weave_client +from weave.trace.client_context import weave_client as weave_client_context +from weave.trace_server import remote_http_trace_server, sqlite_trace_server _current_inited_client = None @@ -20,7 +18,7 @@ def reset(self) -> None: def get_username() -> typing.Optional[str]: - from weave.legacy import wandb_api + from weave.legacy.weave import wandb_api api = wandb_api.get_wandb_api_sync() try: @@ -30,7 +28,7 @@ def get_username() -> typing.Optional[str]: def get_entity_project_from_project_name(project_name: str) -> tuple[str, str]: - from weave.legacy import wandb_api + from weave.legacy.weave import wandb_api fields = project_name.split("/") if len(fields) == 1: @@ -78,7 +76,7 @@ def init_weave( else: _current_inited_client.reset() - from weave.legacy import wandb_api + from weave.legacy.weave import wandb_api # Must init to read ensure we've read auth from the environment, in # case we're on a new thread. diff --git a/weave/trace_server/remote_http_trace_server.py b/weave/trace_server/remote_http_trace_server.py index 842e5c43893..568552c23f9 100644 --- a/weave/trace_server/remote_http_trace_server.py +++ b/weave/trace_server/remote_http_trace_server.py @@ -6,8 +6,8 @@ import tenacity from pydantic import BaseModel, ValidationError -from weave.legacy.environment import weave_trace_server_url -from weave.legacy.wandb_interface import project_creator +from weave.legacy.weave.environment import weave_trace_server_url +from weave.legacy.weave.wandb_interface import project_creator from . import requests from . import trace_server_interface as tsi diff --git a/weave/weave_server.py b/weave/weave_server.py index b07886df75d..05870a1ea1d 100644 --- a/weave/weave_server.py +++ b/weave/weave_server.py @@ -25,7 +25,7 @@ from flask_cors import CORS from werkzeug.exceptions import HTTPException -from weave.legacy import ( +from weave.legacy.weave import ( context_state, engine_trace, environment, @@ -41,8 +41,10 @@ wandb_api, weavejs_fixes, ) -from weave.legacy.language_features.tagging import tag_store -from weave.legacy.server_error_handling import client_safe_http_exceptions_as_werkzeug +from weave.legacy.weave.language_features.tagging import tag_store +from weave.legacy.weave.server_error_handling import ( + client_safe_http_exceptions_as_werkzeug, +) logger = logging.getLogger(__name__) @@ -91,7 +93,7 @@ def execute(self, document, *args, **kwargs): # Ensure these are imported and registered -from weave.legacy import ops +from weave.legacy.weave import ops # NOTE: Fixes flask dev server's auto-reload capability, by forcing it to use # stat mode instead of watchdog mode. It turns out that "import wandb" breaks @@ -110,11 +112,11 @@ def execute(self, document, *args, **kwargs): def import_ecosystem(): - from weave.legacy import ops, panels, panels_py + from weave.legacy.weave import ops, panels, panels_py # Attempt to import MVP ecosystem modules try: - from weave.legacy.ecosystem import langchain, replicate + from weave.legacy.weave.ecosystem import langchain, replicate except ImportError: pass @@ -127,7 +129,7 @@ def import_ecosystem(): # except (ImportError, OSError, wandb.Error): # print("Error: Couldn't import faiss module for Weaveflow.") try: - from weave.legacy.ecosystem import all + from weave.legacy.weave.ecosystem import all except (ImportError, OSError, wandb.Error): pass